logwatch-7.14/0000755000175000017500000000000015134312425013457 5ustar logwatchlogwatchlogwatch-7.14/amavis-logwatch.10000644000175000017500000010155015075123620016632 0ustar logwatchlogwatch.TH AMAVIS-LOGWATCH 1 .ad .fi .SH NAME amavis-logwatch \- An Amavisd-new log parser and analysis utility .SH "SYNOPSIS" .na .nf .fi \fBamavis-logwatch\fR [\fIoptions\fR] [\fIlogfile ...\fR] .SH DESCRIPTION .ad .fi The \fBamavis-logwatch\fR(1) utility is an Amavisd-new log parser that produces summaries, details, and statistics regarding the operation of Amavisd-new (henceforth, simply called Amavis). .PP This utility can be used as a standalone program, or as a Logwatch filter module to produce Amavisd-new summary and detailed reports from within Logwatch. .PP \fBAmavis-logwatch\fR is able to produce a wide range of reports with data grouped and sorted as much as possible to reduce noise and highlight patterns. Brief summary reports provide a quick overview of general Amavis operations and message delivery, calling out warnings that may require attention. Detailed reports provide easy to scan, hierarchically-arranged and organized information, with as much or little detail as desired. .PP Much of the interesting data is available when Amavis' $log_level is set to at least 2. See \fBAmavis Log Level\fR below. .PP \fBAmavis-logwatch\fR outputs two principal sections: a \fBSummary\fR section and a \fBDetailed\fR section. For readability and quick scanning, all event or hit counts appear in the left column, followed by brief description of the event type, and finally additional statistics or count representations may appear in the rightmost column. The following segment from a sample Summary report illustrates: .RS 4 .nf ****** Summary ******************************************** 9 Miscellaneous warnings 20313 Total messages scanned ---------------- 100.00% 1008.534M Total bytes scanned 1,057,524,252 ======== ================================================ 1190 Blocked ------------------------------- 5.86% 18 Malware blocked 0.09% 4 Banned name blocked 0.02% 416 Spam blocked 2.05% 752 Spam discarded (no quarantine) 3.70% 19123 Passed -------------------------------- 94.14% 47 Bad header passed 0.23% 19076 Clean passed 93.91% ======== ================================================ 18 Malware ------------------------------- 0.09% 18 Malware blocked 0.09% 4 Banned -------------------------------- 0.02% 4 Banned file blocked 0.02% 1168 Spam ---------------------------------- 5.75% 416 Spam blocked 2.05% 752 Spam discarded (no quarantine) 3.70% 19123 Ham ----------------------------------- 94.14% 47 Bad header passed 0.23% 19076 Clean passed 93.91% ======== ================================================ 1982 SpamAssassin bypassed 32 Released from quarantine 2 DSN notification (debug supplemental) 2 Bounce unverifiable 2369 Whitelisted 2 Blacklisted 12 MIME error 58 Bad header (debug supplemental) 40 Extra code modules loaded at runtime .fi .RE 0 The report indicates there were 9 general warnings, and \fBAmavis\fR scanned a total of 20313 messages for a total of 1008.53 megabytes or 1,057,524,252 bytes. The next summary groups shows the Blocked / Passed overview, with 1190 Blocked messages (broken down as 18 messages blocked as malware, 4 messages with banned names, 416 spam messages, and 752 discarded messages), and 19123 Passed messages (47 messages with bad headers and 19076 clean messages). The next (optional) summary grouping shows message disposition by contents category. There were 18 malware messages and 4 banned file messages (all blocked), 1168 Spam messages, of which 416 were blocked (quarantined) and 752 discarded. Finally, there were 19123 messages considered to be Ham (i.e. not spam), 47 of which contained bad headers. Additional count summaries for a variety of events are also listed. .PP There are dozens of sub-sections available in the \fBDetailed\fR report, each of whose output can be controlled in various ways. Each sub-section attempts to group and present the most meaningful data at superior levels, while pushing less useful or \fInoisy\fR data towards inferior levels. The goal is to provide as much benefit as possible from smart grouping of data, to allow faster report scanning, pattern identification, and problem solving. Data is always sorted in descending order by count, and then numerically by IP address or alphabetically as appropriate. .PP The following Spam blocked segment from a sample \fBDetailed\fR report illustrates the basic hierarchical level structure of \fBamavis-logwatch\fR: .RS 4 .nf ****** Detailed ******************************************* 19346 Spam blocked ----------------------------------- 756 from@example.com 12 10.0.0.2 12 <> 12 192.168.2.2 12 <> 5 192.168.2.1 ... .fi .RE 0 .PP The \fBamavis-logwatch\fR utility reads from STDIN or from the named Amavis \fIlogfile\fR. Multiple \fIlogfile\fR arguments may be specified, each processed in order. The user running \fBamavis-logwatch\fR must have read permission on each named log file. .PP .SS Options The options listed below affect the operation of \fBamavis-logwatch\fR. Options specified later on the command line override earlier ones. Any option may be abbreviated to an unambiguous length. .IP "\fB--[no]autolearn\fR" .PD 0 .IP "\fB--show_autolearn \fIboolean\fR" .PD Enables (disables) output of the autolearn report. This report is only available if the default Amavis \fB$log_templ\fR has been modified to provide autolearn results in log entries. This can be done by uncommenting two lines in the Amavis program itself (where the default log templates reside), or by correctly adding the \fB$log_templ\fR variable to the \fBamavisd.conf\fR file. See Amavis' \fBREADME.customize\fR and search near the end of the Amavisd program for "autolearn". .IP "\fB--[no]by_ccat_summary\fR" .PD 0 .IP "\fB--show_by_ccat_summary \fIboolean\fR" .PD Enables (disables) the by contents category summary in the \fBSummary\fR section. Default: enabled. .IP "\fB-f \fIconfig_file\fR" .PD 0 .IP "\fB--config_file \fIconfig_file\fR" .PD Use an alternate configuration file \fIconfig_file\fR instead of the default. This option may be used more than once. Multiple configuration files will be processed in the order presented on the command line. See \fBCONFIGURATION FILE\fR below. .IP "\fB--debug \fIkeywords\fR" Output debug information during the operation of \fBamavis-logwatch\fR. The parameter \fIkeywords\fR is one or more comma or space separated keywords. To obtain the list of valid keywords, use --debug xxx where xxx is any invalid keyword. .IP "\fB--detail \fIlevel\fR" Sets the maximum detail level for \fBamavis-logwatch\fR to \fIlevel\fR. This option is global, overriding any other output limiters described below. The \fBamavis-logwatch\fR utility produces a \fBSummary\fR section, a \fBDetailed\fR section, and additional report sections. With \fIlevel\fR less than 5, \fBamavis-logwatch\fR will produce only the \fBSummary\fR section. At \fIlevel\fR 5 and above, the \fBDetailed\fR section, and any additional report sections are candidates for output. Each incremental increase in \fIlevel\fR generates one additional hierarchical sub-level of output in the \fBDetailed\fR section of the report. At \fIlevel\fR 10, all levels are output. Lines that exceed the maximum report width (specified with \fBmax_report_width\fR) will be cut. Setting \fIlevel\fR to 11 will prevent lines in the report from being cut (see also \fB--line_style\fR). .IP "\fB--[no]first_recip_only\fR" .PD 0 .IP "\fB--show_first_recip_only \fIboolean\fR" .PD Specifies whether or not to sort by, and show, only the first recipient when a scanned messages contains multiple recipients. .IP "\fB--help\fR" Print usage information and a brief description about command line options. .IP "\fB--ipaddr_width \fIwidth\fR" Specifies that IP addresses in address/hostname pairs should be printed with a field width of \fIwidth\fR characters. Increasing the default may be useful for systems using long IPv6 addresses. .IP "\fB-l limiter=levelspec\fR" .PD 0 .IP "\fB--limit limiter=levelspec\fR" .PD Sets the level limiter \fIlimiter\fR with the specification \fIlevelspec\fR. .IP "\fB--line_style \fIstyle\fR" Specifies how to handle long report lines. Three styles are available: \fBfull\fR, \fBtruncate\fR, and \fBwrap\fR. Setting \fIstyle\fR to \fBfull\fR will prevent cutting lines to \fBmax_report_width\fR; this is what occurs when \fBdetail\fR is 11 or higher. When \fIstyle\fR is \fBtruncate\fR (the default), long lines will be truncated according to \fBmax_report_width\fR. Setting \fIstyle\fR to \fBwrap\fR will wrap lines longer than \fBmax_report_width\fR such that left column hit counts are not obscured. This option takes precedence over the line style implied by the \fBdetail\fR level. The options \fB--full\fR, \fB--truncate\fR, and \fB--wrap\fR are synonyms. .IP "\fB--nodetail\fR" Disables the \fBDetailed\fR section of the report, and all supplemental reports. This option provides a convenient mechanism to quickly disable all sections under the \fBDetailed\fR report, where subsequent command line options may re-enable one or more sections to create specific reports. .PD 0 .IP "\fB--sarules \fR\`\fIS,H\fR\'" .IP "\fB--sarules default" .PD Enables the SpamAssassin Rules Hit report. The comma-separated \fIS\fR and \fIH\fR arguments are top N values for the Spam and Ham reports, respectively, and can be any integer greater than or equal to 0, or the keyword \fBall\fR. The keyword \fBdefault\fR uses the built-in default values. .IP "\fB--nosarules\fR" Disables the SpamAssassin Rules Hit report. .PD 0 .IP "\fB--sa_timings \fR\fInrows\fR" Enables the SpamAssassin Timings percentiles report. The report can be limited to the top N rows with the \fInrows\fR argument. This report requires Amavis 2.6+ and SpamAssassin 3.3+. .PD .IP "\fB--sa_timings_percentiles \fR\`\fIP1 [P2 ...]\fR\'" Specifies the percentiles shown in the SpamAssassin Timings report. The arguments \fIP1 ...\fR are integers from 0 to 100 inclusive. Their order will be preserved in the report. .IP "\fB--nosa_timings\fR" Disables the SpamAssassin Timings report. .IP "\fB--version\fR" Print \fBamavis-logwatch\fR version information. .PD 0 .IP "\fB--score_frequencies \fR\`\fIB1 [B2 ...]\fR\'" .IP "\fB--score_frequencies default" .PD Enables the Spam Score Frequency report. The arguments \fIB1 ...\fR are frequency distribution buckets, and can be any real numbers. Their order will be preserved in the report. The keyword \fBdefault\fR uses the built-in default values. .IP "\fB--noscore_frequencies\fR" Disables the Spam Score Frequency report. .PD 0 .IP "\fB--score_percentiles \fR\`\fIP1 [P2 ...]\fR\'" .IP "\fB--score_percentiles default" .PD Enables the Spam Score Percentiles report. The arguments \fIP1 ...\fR specify the percentiles shown in the report, and are integers from 0 to 100 inclusive. The keyword \fBdefault\fR uses the built-in default values. .IP "\fB--noscore_percentiles\fR" Disables the Spam Score Percentiles report. .IP "\fB--[no]sect_vars\fR" .PD 0 .IP "\fB--show_sect_vars \fIboolean\fR" .PD Enables (disables) supplementing each \fBDetailed\fR section title with the name of that section's level limiter. The name displayed is the command line option (or configuration file variable) used to limit that section's output. . With the large number of level limiters available in \fBamavis-logwatch\fR, this a convenient mechanism for determining exactly which level limiter affects a section. .IP "\fB--[no]startinfo\fR" .PD 0 .IP "\fB--show_startinfo \fIboolean\fR" .PD Enables (disables) the Amavis startup report showing most recent Amavis startup details. .IP "\fB--[no]summary\fR" .IP "\fB--show_summary\fR" Enables (disables) displaying of the the \fBSummary\fR section of the report. The variable Amavis_Show_Summary in used in a configuration file. .IP "\fB--syslog_name \fInamepat\fR" Specifies the syslog service name that \fBamavis-logwatch\fR uses to match syslog lines. Only log lines whose service name matches the perl regular expression \fInamepat\fR will be used by \fBamavis-logwatch\fR; all non-matching lines are silently ignored. This is useful when a pre-installed Amavis package uses a name other than the default (\fBamavis\fR). \fBNote:\fR if you use parenthesis in your regular expression, be sure they are cloistering and not capturing: use \fB(?:\fIpattern\fB)\fR instead of \fB(\fIpattern\fB)\fR. .PD 0 .IP "\fB--timings \fR\fIpercent\fR" Enables the Amavis Scan Timings percentiles report. The report can be top N-percent limited with the \fIpercent\fR argument. .PD .IP "\fB--timings_percentiles \fR\`\fIP1 [P2 ...]\fR\'" Specifies the percentiles shown in the Scan Timings report. The arguments \fIP1 ...\fR are integers from 0 to 100 inclusive. Their order will be preserved in the report. .IP "\fB--notimings\fR" Disables the Amavis Scan Timings report. .IP "\fB--version\fR" Print \fBamavis-logwatch\fR version information. .SS Level Limiters .PP The output of every section in the \fBDetailed\fR report is controlled by a level limiter. The name of the level limiter variable will be output when the \fBsect_vars\fR option is set. Level limiters are set either via command line in standalone mode with \fB--limit \fIlimiter\fB=\fIlevelspec\fR option, or via configuration file variable \fB$amavis_\fIlimiter\fB=\fIlevelspec\fR. Each limiter requires a \fIlevelspec\fR argument, which is described below in \fBLEVEL CONTROL\fR. The list of level limiters is shown below. .de TQ . br . ns . TP \\$1 .. .PD 0 .PP Amavis major contents category (ccatmajor) sections, listed in order of priority: VIRUS, BANNED, UNCHECKED, SPAM, SPAMMY, BADH, OVERSIZED, MTA, CLEAN. .IP "\fBMalwareBlocked" .IP "\fBMalwarePassed" Blocked or passed messages that contain malware (ccatmajor: VIRUS). .IP "\fBBannedNameBlocked" .IP "\fBBannedNamePassed" Blocked or passed messages that contain banned names in MIME parts (ccatmajor: BANNED). .IP "\fBUncheckedBlocked" .IP "\fBUncheckedPassed" Blocked or passed messages that were not checked by a virus scanner or SpamAssassin (Amavis ccatmajor: UNCHECKED). .IP "\fBSpamBlocked" .IP "\fBSpamPassed" Blocked or passed messages that were considered spam that reached kill level (Amavis ccatmajor: SPAM) .IP "\fBSpammyBlocked" .IP "\fBSpammyPassed" Blocked or passed messages that were considered spam, but did not reach kill level (Amavis ccatmajor: SPAMMY) .IP "\fBBadHeaderBlocked" .IP "\fBBadHeaderPassed" Blocked or passed messages that contain bad mail headers (ccatmajor: BAD-HEADER). .IP "\fBOversizedBlocked" .IP "\fBOversizedPassed" Blocked or passed messages that were considered oversized (Amavis ccatmajor: OVERSIZED). .IP "\fBMtaBlocked" .IP "\fBMtaPassed" Blocked or passed messages due to failure to re-inject to MTA (Amavis ccatmajor: MTA-BLOCKED). Occurrences of this event indicates a configuration problem. [ note: I don't believe mtapassed occurs, but exists for completeness.] .IP "\fBOtherBlocked" .IP "\fBOtherPassed" Blocked or passed messages that are not any of other major contents categories (Amavis ccatmajor: OTHER). .IP "\fBTempFailBlocked" .IP "\fBTempfailPassed" Blocked or passed messages that had a temporary failure (Amavis ccatmajor: TEMPFAIL) .IP "\fBCleanBlocked" .IP "\fBCleanPassed " Messages blocked or passed which were considered clean (Amavis ccatmajor: CLEAN; i.e. non-spam, non-viral). .PP Other sections, arranged alphabetically: .IP "\fBAvConnectFailure" Problems connecting to Anti-Virus scanner(s). .IP "\fBAvTimeout" Timeouts awaiting responses from Anti-Virus scanner(s). .IP "\fBArchiveExtract" Archive extraction problems. .IP "\fBBadHeaderSupp" Supplemental debug information regarding messages containing bad mail headers. .IP "\fBBayes" Messages frequencies by Bayesian probability buckets. .IP "\fBBadAddress" Invalid mail address syntax. .IP "\fBBlacklisted" Messages that were (soft-)blacklisted. See also Whitelisted below. .IP "\fBBounceKilled" .IP "\fBBounceRescued" .IP "\fBBounceUnverifiable" Disposition of incoming bounce messages (DSNs). .IP "\fBContentType" MIME attachment breakdown by type/subtype. .IP "\fBDccError" Errors encountered with or returned by DCC. .IP "\fBDefangError" Errors encountered during defang process. .IP "\fBDefanged" Messages defanged (rendered harmless). .IP "\fBDsnNotification" Errors encountered during attempt to send delivery status notification. .IP "\fBDsnSuppressed" Delivery status notification (DSN) intentionally suppressed. .IP "\fBExtraModules" Additional code modules Amavis loaded during runtime. .IP "\fBFakeSender" Forged sender addresses, as determined by Amavis. .IP "\fBFatal" Fatal events. These are presented at the top of the report, as they may require attention. .IP "\fBLocalDeliverySkipped" Failures delivering to a local address. .IP "\fBMalwareByScanner" Breakdown of malware by scanner(s) that detected the malware. .IP "\fBMimeError" Errors encountered during MIME extraction. .IP "\fBPanic" Panic events. These are presented at the top of the report, as they may require attention. .IP "\fBp0f" Passive fingerprint (p0f) hits, grouped by mail contents type (virus, unchecked, banned, spam, ham), next by operating system genre, and finally by IP address. Note: Windows systems are refined by Windows OS version, whereas versions of other operating systems are grouped generically. .IP "\fBReleased" Messages that were released from Amavis quarantine. .IP "\fBSADiags" Diagnostics as reported from SpamAssassin. .IP "\fBSmtpResponse" SMTP responses received during dialog with MTA. These log entries are primarly debug. .IP "\fBTmpPreserved" Temporary directories preserved by Amavis when some component encounters a problem or failure. Directories listed and their corresponding log entries should be evaluated for problems. .IP "\fBVirusScanSkipped" Messages that could not be scanned by a virus scanner. .IP "\fBWarning" Warning events not categorized in specific warnings below. These are presented at the top of the report, as they may require attention. .IP "\fBWarningAddressModified" Incomplete email addresses modified by Amavis for safety. .IP "\fBWarningNoQuarantineId" Attempts to release a quarantined message that did not contain an X-Quarantine-ID header. .IP "\fBWarningSecurity \fIlevelspec\fR" Insecure configuration or utility used by Amavis. .IP "\fBWarningSmtpShutdown" Failures during SMTP conversation with MTA. .IP "\fBWarningSql" Failures to communicate with, or error replies from, SQL service. .IP "\fBWhitelisted" Messages that were (soft-)whitelisted. See also Blacklisted above. .PD .SH LEVEL CONTROL .ad .fi The \fBDetailed\fR section of the report consists of a number of sub-sections, each of which is controlled both globally and independently. Two settings influence the output provided in the \fBDetailed\fR report: a global detail level (specified with \fB--detail\fR) which has final (big hammer) output-limiting control over the \fBDetailed\fR section, and sub-section specific detail settings (small hammer), which allow further limiting of the output for a sub-section. Each sub-section may be limited to a specific depth level, and each sub-level may be limited with top N or threshold limits. The \fIlevelspec\fR argument to each of the level limiters listed above is used to accomplish this. It is probably best to continue explanation of sub-level limiting with the following well-known outline-style hierarchy, and some basic examples: .nf level 0 level 1 level 2 level 3 level 4 level 4 level 2 level 3 level 4 level 4 level 4 level 3 level 4 level 3 level 1 level 2 level 3 level 4 .fi .PP The simplest form of output limiting suppresses all output below a specified level. For example, a \fIlevelspec\fR set to "2" shows only data in levels 0 through 2. Think of this as collapsing each sub-level 2 item, thus hiding all inferior levels (3, 4, ...), to yield: .nf level 0 level 1 level 2 level 2 level 1 level 2 .fi .PP 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. Consider a dictionary spam attack, which produces very lengthy lists of hit-once recipient email or IP addresses. Each sub-level in the hierarchy can be threshold-limited by setting the \fIlevelspec\fR appropriately. Setting \fIlevelspec\fR to the value "2::5" will suppress any data at level 2 that does not exceed a hit count of 5. .PP Perhaps producing a top N list, such as top 10 senders, is desired. A \fIlevelspec\fR of "3:10:" limits level 3 data to only the top 10 hits. .PP 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: .IP "\fIl\fR" Specifies the maximum level to be output for this sub-section, with a range from 0 to 10. if \fIl\fR is 0, no levels will be output, effectively disabling the sub-section (level 0 data is already provided in the Summary report, so level 1 is considered the first useful level in the \fBDetailed\fR report). Higher values will produce output up to and including the specified level. .IP "\fIl\fB.\fIn\fR" Same as above, with the addition that \fIn\fR limits this section's level 1 output to the top \fIn\fR items. The value for \fIn\fR can be any integer greater than 1. (This form of limiting has less utility than the syntax shown below. It is provided for backwards compatibility; users are encouraged to use the syntax below). .IP "\fIl\fB:\fIn\fB:\fIt\fR" This triplet specifies level \fIl\fR, top \fIn\fR, and minimum threshold \fIt\fR. Each of the values are integers, with \fIl\fR being the level limiter as described above, \fIn\fR being a top \fIn\fR limiter for the level \fIl\fR, and \fIt\fR being the threshold limiter for level \fIl\fR. When both \fIn\fR and \fIt\fR are specified, \fIn\fR has priority, allowing top \fIn\fR lists (regardless of threshold value). If the value of \fIl\fR is omitted, the specified values for \fIn\fR and/or \fIt\fR are used for all levels available in the sub-section. This permits a simple form of wildcarding (eg. place minimum threshold limits on all levels). However, specific limiters always override wildcard limiters. The first form of level limiter may be included in \fIlevelspec\fR to restrict output, regardless of how many triplets are present. .PP All three forms of limiters are effective only when \fBamavis-logwatch\fR's detail level is 5 or greater (the \fBDetailed\fR section is not activated until detail is at least 5). .PP See the \fBEXAMPLES\fR section for usage scenarios. .SH CONFIGURATION FILE .ad \fBAmavis-logwatch\fR can read configuration settings from a configuration file. Essentially, any command line option can be placed into a configuration file, and these settings are read upon startup. Because \fBamavis-logwatch\fR can run either standalone or within Logwatch, to minimize confusion, \fBamavis-logwatch\fR inherits Logwatch's configuration file syntax requirements and conventions. These are: .IP \(bu 4'. White space lines are ignored. .IP \(bu 4'. Lines beginning with \fB#\fR are ignored .IP \(bu 4'. Settings are of the form: .nf \fIoption\fB = \fIvalue\fR .fi .IP \(bu 4'. Spaces or tabs on either side of the \fB=\fR character are ignored. .IP \(bu 4'. Any \fIvalue\fR protected in double quotes will be case-preserved. .IP \(bu 4'. All other content is reduced to lowercase (non-preserving, case insensitive). .IP \(bu 4'. All \fBamavis-logwatch\fR configuration settings must be prefixed with "\fB$amavis_\fR" or \fBamavis-logwatch\fR will ignore them. .IP \(bu 4'. When running under Logwatch, any values not prefixed with "\fB$amavis_\fR" are consumed by Logwatch; it only passes to \fBamavis-logwatch\fR (via environment variable) settings it considers valid. .IP \(bu 4'. The values \fBTrue\fR and \fBYes\fR are converted to 1, and \fBFalse\fR and \fBNo\fR are converted to 0. .IP \(bu 4'. Order of settings is not preserved within a configuration file (since settings are passed by Logwatch via environment variables, which have no defined order). .PP To include a command line option in a configuration file, prefix the command line option name with the word "\fB$amavis_\fR". The following configuration file setting and command line option are equivalent: .nf \fB$amavis_Line_Style = Truncate\fR \fB--line_style Truncate\fR .fi Level limiters are also prefixed with \fB$amavis_\fR, but on the command line are specified with the \fB--limit\fR option: .nf \fB$amavis_SpamBlocked = 2\fR \fB--limit SpamBlocked=2\fR .fi The order of command line options and configuration file processing occurs as follows: 1) The default configuration file is read if it exists and no \fB--config_file\fR was specified on a command line. 2) Configuration files are read and processed in the order found on the command line. 3) Command line options override any options already set either via command line or from any configuration file. Command line options are interpreted when they are seen on the command line, and later options will override previously set options. .SH "EXIT STATUS" .na .nf .ad .fi The \fBamavis-logwatch\fR utility exits with a status code of 0, unless an error occurred, in which case a non-zero exit status is returned. .SH "EXAMPLES" .na .nf .ad .fi .SS Running Standalone \fBNote:\fR \fBamavis-logwatch\fR reads its log data from one or more named Amavis log files, or from STDIN. For brevity, where required, the examples below use the word \fIfile\fR as the command line argument meaning \fI/path/to/amavis.log\fR. Obviously you will need to substitute \fIfile\fR with the appropriate path. .nf .PP To run \fBamavis-logwatch\fR in standalone mode, simply run: .nf .RS 4 .PP \fBamavis-logwatch \fIfile\fR .RE 0 .nf .PP A complete list of options and basic usage is available via: .nf .RS 4 .PP \fBamavis-logwatch --help\fR .RE 0 .nf .PP To print a summary only report of Amavis log data: .nf .RS 4 .PP \fBamavis-logwatch --detail 1 \fIfile\fR .RE 0 .fi .PP To produce a summary report and a one-level detail report for May 25th: .nf .RS 4 .PP \fBgrep 'May 25' \fIfile\fB | amavis-logwatch --detail 5\fR .RE 0 .fi .PP To produce only a top 10 list of Sent email domains, the summary report and detailed reports are first disabled. Since commands line options are read and enabled left-to-right, the Sent section is re-enabled to level 1 with a level 1 top 10 limiter: .nf .RS 4 .PP \fBamavis-logwatch --nosummary --nodetail \\ --limit spamblocked '1 1:10:' \fIfile\fR .RE 0 .fi .PP The following command and its sample output shows a more complex level limiter example. The command gives the top 4 spam blocked recipients (level 1), and under with each recipient the top 2 sending IPs (level 2) and finally below that, only envelope from addresses (level 3) with hit counts greater than 6. Ellipses indicate top N or threshold-limited data: .nf .RS 4 .PP \fBamavis-logwatch --nosummary --nodetail \\ --limit spamblocked '1:4: 2:2: 3::6' \fIfile\fR .nf 19346 Spam blocked ----------------------------------- 756 joe@example.com 12 10.0.0.1 12 <> 12 10.99.99.99 12 <> ... 640 fred@example.com 8 10.0.0.1 8 <> 8 192.168.3.19 8 <> ... 595 peter@sample.net 8 10.0.0.1 8 <> 7 192.168.3.3 7 <> ... 547 paul@example.us 8 192.168.3.19 8 <> 7 10.0.0.1 7 <> ... ... .fi .RE 0 .fi .SS Running within Logwatch \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. Since version 7.3.6, STDOUT is the default output destination, and the \fB--print\fR option has been replaced 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. .PP To print a summary report for today's Amavis log data: .nf .RS 4 .PP \fBlogwatch --service amavis --range today --detail 1\fR .RE 0 .nf .PP To print a report for today's Amavis log data, with one level of detail in the \fBDetailed\fR section: .nf .RS 4 .PP \fBlogwatch --service amavis --range today --detail 5\fR .RE 0 .fi .PP To print a report for yesterday, with two levels of detail in the \fBDetailed\fR section: .nf .RS 4 .PP \fBlogwatch --service amavis --range yesterday --detail 6\fR .RE 0 .fi .PP To print a report from Dec 12th through Dec 14th, with four levels of detail in the \fBDetailed\fR section: .nf .RS 4 .PP \fBlogwatch --service amavis --range \\ 'between 12/12 and 12/14' --detail 8\fR .RE 0 .PP To print a report for today, with all levels of detail: .nf .RS 4 .PP \fBlogwatch --service amavis --range today --detail 10\fR .RE 0 .PP Same as above, but leaves long lines uncropped: .nf .RS 4 .PP \fBlogwatch --service amavis --range today --detail 11\fR .RE 0 .SS "Amavis Log Level" .PP Amavis provides additional log information when the variable \fB$log_level\fR is increased above the default 0 value. This information is used by the \fBamavis-logwatch\fR utility to provide additional reports, not available with the default \fB$log_level\fR=0 value. A \fB$log_level\fR of 2 is suggested. .PP If you prefer not to increase the noise level in your main mail or Amavis logs, you can configure syslog to log Amavis' output to multiple log files, where basic log entries are routed to your main mail log(s) and more detailed entries routed to an Amavis-specific log file used to feed the \fBamavis-logwatch\fR utility. .PP A convenient way to accomplish this is to change the Amavis configuration variables in \fBamavisd.conf\fR as shown below: .nf amavisd.conf: $log_level = 2; $syslog_facility = 'local5'; $syslog_priority = 'debug'; .fi .PP This increases \fB$log_level\fR to 2, and sends Amavis' log entries to an alternate syslog facility (eg. \fBlocal5\fR, user), which can then be routed to one or more log files, including your main mail log file: .nf syslog.conf: #mail.info -/var/log/maillog mail.info;local5.notice -/var/log/maillog local5.info -/var/log/amavisd-info.log .fi .PP \fBAmavis\fR' typical \fB$log_level\fR 0 messages will be directed to both your maillog and to the \fBamavisd-info.log\fR file, but higher \fB$log_level\fR messages will only be routed to the \fBamavisd-info.log\fR file. For additional information on Amavis' logging, search the file \fBRELEASE_NOTES\fR in the Amavis distribution for: .nf "syslog priorities are now dynamically derived" .fi .SH "ENVIRONMENT" .na .nf .ad .fi The \fBamavis-logwatch\fR program uses the following (automatically set) environment variables when running under Logwatch: .IP \fBLOGWATCH_DETAIL_LEVEL\fR This is the detail level specified with the Logwatch command line argument \fB--detail\fR or the \fBDetail\fR setting in the ...conf/services/amavis.conf configuration file. .IP \fBLOGWATCH_DEBUG\fR This is the debug level specified with the Logwatch command line argument \fB--debug\fR. .IP \fBamavis_\fIxxx\fR The Logwatch program passes all settings \fBamavis_\fIxxx\fR in the configuration file ...conf/services/amavis.conf to the \fBamavis\fR filter (which is actually named .../scripts/services/amavis) via environment variable. .SH "FILES" .na .nf .SS Standalone mode .IP "/usr/local/bin/amavis-logwatch" The \fBamavis-logwatch\fR program .IP "/usr/local/etc/amavis-logwatch.conf" The \fBamavis-logwatch\fR configuration file in standalone mode .SS Logwatch mode .IP "/etc/logwatch/scripts/services/amavis" The Logwatch \fBamavis\fR filter .IP "/etc/logwatch/conf/services/amavis.conf" The Logwatch \fBamavis\fR filter configuration file .SH "SEE ALSO" .na .nf logwatch(8), system log analyzer and reporter .SH "README FILES" .na .ad .nf README, an overview of \fBamavis-logwatch\fR Changes, the version change list history Bugs, a list of the current bugs or other inadequacies Makefile, the rudimentary installer LICENSE, the usage and redistribution licensing terms .SH "LICENSE" .na .nf .ad Covered under the included MIT/X-Consortium License: http://www.opensource.org/licenses/mit-license.php .SH "AUTHOR(S)" .na .nf Mike Cappella .fi The original \fBamavis\fR Logwatch filter was written by Jim O'Halloran, and has had many contributors over the years. They are entirely not responsible for any errors, problems or failures since the current author's hands have touched the source code. logwatch-7.14/postfix-logwatch.10000644000175000017500000010636315075123620017055 0ustar logwatchlogwatch.TH POSTFIX-LOGWATCH 1 .ad .fi .SH NAME postfix-logwatch \- A Postfix log parser and analysis utility .SH "SYNOPSIS" .na .nf .fi \fBpostfix-logwatch\fR [\fIoptions\fR] [\fIlogfile ...\fR] .SH DESCRIPTION .ad .fi The \fBpostfix-logwatch\fR(1) utility is a Postfix MTA log parser that produces summaries, details, and statistics regarding the operation of Postfix. .PP This utility can be used as a standalone program, or as a Logwatch filter module to produce Postfix summary and detailed reports from within Logwatch. .PP \fBPostfix-logwatch\fR is able to produce a wide range of reports with data grouped and sorted as much as possible to reduce noise and highlight patterns. Brief summary reports provide a quick overview of general Postfix operations and message delivery, calling out warnings that may require attention. Detailed reports provide easy to scan, hierarchically-arranged and organized information, with as much or little detail as desired. .PP \fBPostfix-logwatch\fR outputs two principal sections: a \fBSummary\fR section and a \fBDetailed\fR section. For readability and quick scanning, all event or hit counts appear in the left column, followed by brief description of the event type, and finally additional statistics or count representations may appear in the rightmost column. The following segment from a sample Summary report illustrates: .RS 4 .nf ****** Summary ******************************************** 81 *Warning: Connection rate limit reached (anvil) 146 Warned 68.310M Bytes accepted 71,628,177 97.645M Bytes delivered 102,388,245 ======== ================================================ 3464 Accepted 41.44% 4895 Rejected 58.56% -------- ------------------------------------------------ 8359 Total 100.00% ======== ================================================ .fi .RE 0 The report warns that anvil's connection rate was hit 81 times, a Postfix access check WARN action was logged 146 times, and a total of 68.310 megabytes (71,628,177 bytes) were accepted into the Postfix system, delivering 97.645 megabytes of data (due to multiple recipients). The Accepted and Rejected lines show that Postfix accepted 3464 (41.44% of the total messages) and rejected 4895 (the remaining 58.56%) of the 8359 total messages (temporary rejects show up elsewhere). .PP There are dozens of sub-sections available in the \fBDetailed\fR report, each of whose output can be controlled in various ways. Each sub-section attempts to group and present the most meaningful data at superior levels, while pushing less useful or \fInoisy\fR data towards inferior levels. The goal is to provide as much benefit as possible from smart grouping of data, to allow faster report scanning, pattern identification, and problem solving. Data is always sorted in descending order by count, and then numerically by IP address or alphabetically as appropriate. .PP The following MX errors segment from a sample \fBDetailed\fR report illustrates the basic hierarchical level structure of \fBpostfix-logwatch\fR: .RS 4 .nf ****** Detailed ******************************************* 261 MX errors -------------------------------------- 261 Unable to look up MX host 222 Host not found 73 foolishspammer.local 60 completely.bogus.domain.example 11 friend.example.com 39 No address associated with hostname 23 dummymx.sample.net 16 pushn.spam.sample.com .fi .RE 0 .PP The \fBpostfix-logwatch\fR utility reads from STDIN or from the named Postfix \fIlogfile\fR. Multiple \fIlogfile\fR arguments may be specified, each processed in order. The user running \fBpostfix-logwatch\fR must have read permission on each named log file. .PP .SS Options The options listed below affect the operation of \fBpostfix-logwatch\fR. Options specified later on the command line override earlier ones. Any option may be abbreviated to an unambiguous length. .IP "\fB-f \fIconfig_file\fR" .PD 0 .IP "\fB--config_file \fIconfig_file\fR" .PD Use an alternate configuration file \fIconfig_file\fR instead of the default. This option may be used more than once. Multiple configuration files will be processed in the order presented on the command line. See \fBCONFIGURATION FILE\fR below. .IP "\fB--debug \fIkeywords\fR" Output debug information during the operation of \fBpostfix-logwatch\fR. The parameter \fIkeywords\fR is one or more comma or space separated keywords. To obtain the list of valid keywords, use --debug xxx where xxx is any invalid keyword. .IP "\fB--[no]delays\fR" Enables (disables) output of the message delays percentiles report. The delays percentiles report shows percentiles for each of the 4 delivery latency times reported by Postfix (available in version 2.3 and later) in the form \fBdelays=\fIa\fR/\fIb\fR/\fIc\fR/\fId\fR, where \fIa\fR is the amount of time before the active queue (includes time for previous delivery attempts and time in the deferred queue), \fIb\fR is the amount of time in the active queue up to delivery agent handoff, \fIc\fR is the amount of time spent making connections (including DNS, HELO and TLS) and \fId\fR is the amount of time spent delivering the message. The total delay shown comes from the \fBdelay=\fR field in a message delivery log line. \fBNote:\fR This report may consume a large amount of memory; if you have no use for it, disable the delays report. .IP "\fB--delays_percentiles \fIp1 [p2 ...]\fR" Specifies the percentiles to be used in the message delays percentiles report. The percentiles \fIp1\fR, \fIp2\fR, \fI...\fR range from 0 to 100, inclusively. The order of the list is not sorted - the report will output the percentiles columns in the order you specify. .IP "\fB--detail \fIlevel\fR" Sets the maximum detail level for \fBpostfix-logwatch\fR to \fIlevel\fR. This option is global, overriding any other output limiters described below. The \fBpostfix-logwatch\fR utility produces a \fBSummary\fR section, a \fBDetailed\fR section, and additional report sections. With \fIlevel\fR less than 5, \fBpostfix-logwatch\fR will produce only the \fBSummary\fR section. At \fIlevel\fR 5 and above, the \fBDetailed\fR section, and any additional report sections are candidates for output. Each incremental increase in \fIlevel\fR generates one additional hierarchical sub-level of output in the \fBDetailed\fR section of the report. At \fIlevel\fR 10, all levels are output. Lines that exceed the maximum report width (specified with \fBmax_report_width\fR) will be cut. Setting \fIlevel\fR to 11 will prevent lines in the report from being cut (see also \fB--line_style\fR). .IP "\fB--help\fR" Print usage information and a brief description about command line options. .IP "\fB--ignore_service \fIpattern\fR" Ignore log lines that contain the postfix service name \fBpostfix/\fIservice\fR. The parameter \fIservice\fR is a regular expression. \fBNote:\fR if you use parenthesis in your regular expression, be sure they are cloistering and not capturing: use \fB(?:\fIpattern\fB)\fR instead of \fB(\fIpattern\fB)\fR. .IP "\fB--ipaddr_width \fIwidth\fR" Specifies that IP addresses in address/hostname pairs should be printed with a field width of \fIwidth\fR characters. Increasing the default may be useful for systems using long IPv6 addresses. .IP "\fB-l limiter=levelspec\fR" .PD 0 .IP "\fB--limit limiter=levelspec\fR" .PD Sets the level limiter \fIlimiter\fR with the specification \fIlevelspec\fR. .IP "\fB--line_style \fIstyle\fR" Specifies how to handle long report lines. Three styles are available: \fBfull\fR, \fBtruncate\fR, and \fBwrap\fR. Setting \fIstyle\fR to \fBfull\fR will prevent cutting lines to \fBmax_report_width\fR; this is what occurs when \fBdetail\fR is 11 or higher. When \fIstyle\fR is \fBtruncate\fR (the default), long lines will be truncated according to \fBmax_report_width\fR. Setting \fIstyle\fR to \fBwrap\fR will wrap lines longer than \fBmax_report_width\fR such that left column hit counts are not obscured. This option takes precedence over the line style implied by the \fBdetail\fR level. The options \fB--full\fR, \fB--truncate\fR, and \fB--wrap\fR are synonyms. .IP "\fB--[no]long_queue_ids\fR" Enables (disables) interpretation of long queue IDs in Postfix (>= 2.9) logs. .IP "\fB--nodetail\fR" Disables the \fBDetailed\fR section of the report, and all supplemental reports. This option provides a convenient mechanism to quickly disable all sections under the \fBDetailed\fR report, where subsequent command line options may re-enable one or more sections to create specific reports. .IP "\fB--[no]summary\fR" .IP "\fB--show_summary\fR" Enables (disables) displaying of the the \fBSummary\fR section of the report. The variable postfix_Show_Summary in used in a configuration file. .IP "\fB--recipient_delimiter \fIdelimiter\fR" Split email delivery addresses using the recipient delimiter character \fIdelimiter\fR. This should generally match the \fBrecipient_delimiter\fR specified in the Postfix parameter file \fBmain.cf\fR, or the default value indicated in \fBpostconf -d recipient_delimiter\fR. This is very useful for obtaining per-alias statistics when a recipient delimiter is used for mail delivery. .IP "\fB--reject_reply_patterns \fIr1 [r2 ...]\fR" Specifies the list of reject reply patterns used to create reject groups. Each entry in the list \fIr1 [r2 ...]\fR must be either a three character regular expression reply code of the form [45][0-9.][0-9.], or the word "Warn". The "." in the regular expression is a literal dot which matches any reject reply subcode; this wildcarding allows creation of broad rejects groups. List order is preserved, in that reject reports will be output in the same order as the entries in the list. Specific reject reply codes will take priority over wildcard patterns, regardless of the list order. The default list is "5.. 4.. Warn", which creates three groups of rejects: permanent rejects, temporary reject failures, and reject warnings (as in warn_if_reject). This feature allows, for example, distinguishing 421 transmission channel closures from 45x errors (eg. 450 mailbox unavailable, 451 local processing errors, 452 insufficient storage). Such a grouping would be configured with the list: "421 4.. 5.. Warn". See RFC 2821 for more information about reply codes. See also \fBCONFIGURATION FILE\fR regarding using \fBreject_reply_patterns\fR within a configuration file. .IP "\fB--[no]sect_vars\fR" .PD 0 .IP "\fB--show_sect_vars \fIboolean\fR" .PD Enables (disables) supplementing each \fBDetailed\fR section title with the name of that section's level limiter. The name displayed is the command line option (or configuration file variable) used to limit that section's output. . With the large number of level limiters available in \fBpostfix-logwatch\fR, this a convenient mechanism for determining exactly which level limiter affects a section. .IP "\fB--syslog_name \fInamepat\fR" Specifies the syslog service name that \fBpostfix-logwatch\fR uses to match syslog lines. Only log lines whose service name matches the perl regular expression \fInamepat\fR will be used by \fBpostfix-logwatch\fR; all non-matching lines are silently ignored. This is useful when a pre-installed Postfix package uses a name other than the default (\fBpostfix\fR), or when multiple Postfix instances are in use and per-instance reporting is desired. The pattern \fInamepat\fR should match the \fBsyslog_name\fR configuration parameter specified in the Postfix parameter file \fBmain.cf\fR, the master control file \fBmaster.cf\fR, or the default value as indicated by the output of \fBpostconf -d syslog_name\fR. \fBNote:\fR if you use parenthesis in your regular expression, be sure they are cloistering and not capturing: use \fB(?:\fIpattern\fB)\fR instead of \fB(\fIpattern\fB)\fR. .IP "\fB--[no]unknown\fR" .PD 0 .IP "\fB--show_unknown \fIboolean\fR" .PD Enables (disables) display of the postfix-generated name of 'unknown' in formated IP/hostname pairs in \fBDetailed\fR reports. Default: enabled. .IP "\fB--version\fR" Print \fBpostfix-logwatch\fR version information. .SS Level Limiters .PP The output of every section in the \fBDetailed\fR report is controlled by a level limiter. The name of the level limiter variable will be output when the \fBsect_vars\fR option is set. Level limiters are set either via command line in standalone mode with \fB--limit \fIlimiter\fB=\fIlevelspec\fR option, or via configuration file variable \fB$postfix_\fIlimiter\fB=\fIlevelspec\fR. Each limiter requires a \fIlevelspec\fR argument, which is described below in \fBLEVEL CONTROL\fR. The list of level limiters is shown below. There are several level limiters that control reject sub-sections (eg. \fBrejectbody\fR, \fBrejectsender\fR, etc.). 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, with the prefix \fB###\fR. To use one of these reject limiters, substitute \fB###\fR with one of the reject reply codes in effect, replacing each dot with an \fBx\fR character. For example, using the default \fBreject_reply_patterns\fR list of "5.. 4.. Warn", three \fBrejectbody\fR variants are valid: \fB--limit 5xxrejectbody\fR, \fB--limit 4xxrejectbody\fR and \fB--limit warnrejectbody\fR. As a convenience, you may entirely eliminate the \fB###\fR prefix, and instead use the bare \fBreject\fIXXX\fR option, and all reject level limiter variations will be auto-generated based on the \fBreject_reply_patterns\fR list. For example, the command line segment: .nf ... --reject_reply_patterns "421 5.." \\ --limit rejectrbl="1:10:" .fi would automatically become: .nf ... --reject_reply_patterns "421 5.." \\ --limit 421rejectrbl="1:10:" --limit 5xxrejectrbl="1:10:" .fi See \fBreject_reply_patterns\fR above, and comments in the configuration file \fBpostfix-logwatch.conf\fR. .de TQ . br . ns . TP \\$1 .. [ THIS SECTION IS NOT YET COMPLETE ] .PD 0 .IP "\fBAttrError" Errors obtaining attribute data from service. .IP "\fBBCCed" Messages that triggered access, header_checks or body_checks BCC action. (postfix 2.6 experimental branch) .IP "\fBBounceLocal" .IP "\fBBounceRemote" Local and remote bounces. A bounce is considered a local bounce if the relay was one of none, local, virtual, avcheck, maildrop or 127.0.0.1. .IP "\fBByIpRejects" Regrouping by client host IP address of all 5xx (permanent) reject variants. .IP "\fBCommunicationError" Postfix errors talking to one of its services. .IP "\fBAnvil" Anvil rate or concurrency limits. .IP "\fBConnectionInbound" Connections made to the \fBsmtpd\fR server. .IP "\fBConnectionLostInbound" Connections lost to the \fBsmtpd\fR server. .IP "\fBConnectionLostOutbound" Connections lost during \fBsmtp\fR communications with remote MTA. .IP "\fBConnectToFailure" Failures reported by \fBsmtp\fR when connecting to remote MTA. .IP "\fBDatabaseGeneration" Warnings noted when binary database map file requires \fBpostmap\fR update from newer source file. .IP "\fBDeferrals" .IP "\fBDeferred" Message delivery deferrals. A single \fBdeferred\fR message will have one or more \fBdeferrals\fR many times. .IP "\fBDeliverable" Address verification indicates recipient address is deliverable. .IP "\fBDelivered" Number of messages handed-off to a delivery agent such as local or virtual. .IP "\fBDiscarded" Messages that triggered access, header_checks or body_checks DISCARD action. .IP "\fBDNSError" Any one of several errors encountered during DNS lookups. .IP "\fBEnvelopeSenderDomains" List of sending domains. (2 levels: envelope sender domain, localpart) .IP "\fBEnvelopeSenders" List of envelope senders. (1 level: envelope sender) .IP "\fBError" Postfix general \fBerror\fR messages. .IP "\fBFatalConfigError" Fatal main.cf or master.cf configuration errors. .IP "\fBFatalError" Postfix general \fBfatal\fR messages. .IP "\fBFiltered" Messages that triggered access, header_checks or body_checks FILTER action. .IP "\fBForwarded" Messages forwarded by MDA for one address class to another (eg. local -> virtual). .IP "\fBHeloError" XXXXXXXXXXX .IP "\fBHold" Messages that were placed on hold by postsuper, or triggered by access, header_checks or body_checks HOLD action. .IP "\fBHostnameValidationError" Invalid hostname detected. .IP "\fBHostnameVerification" Lookup of hostname does not map back to the IP of the peer (ie. the remote system connecting to \fBsmtpd\fR). Also known as forward-confirmed reverse DNS (FCRDNS). When the reverse name has no DNS entry, the message "host not found, try again" is included; otherwise, it is not (e.g. when the reverse has some IP address, but not the one Postfix expects). .IP "\fBIllegalAddrSyntax" Illegal syntax in an email address provided during the MAIL FROM or RCPT TO dialog. .IP "\fBLdapError" Any LDAP errors during LDAP lookup. .IP "\fBMailerLoop" An MX lookup for the best mailer to use to deliver mail would result in a sending to ourselves. .IP "\fBMapProblem" Problem with an access table map that needs correcting. .IP "\fBMessageWriteError" Postfix encountered an error when trying to create a message file somewhere in the spool directory. .IP "\fBNumericHostname" A hostname was found that was numeric, instead of alphabetic. .IP "\fBPanicError" Postfix general \fBpanic\fR messages. .IP "\fBPixWorkaround" Workarounds were enabled to avoid remote Cisco PIX SMTP "fixups". .IP "\fBPolicydWeight" Summarization of policyweight/policydweight results. .IP "\fBPolicySpf" Summarization of PolicySPF results. .IP "\fBPostgrey" Summarization of Postgrey results. .IP "\fBPostscreen" Summarization of 2.7's postscreen and verify services. .IP "\fBDNSBLog" Summarization of 2.7's dnsblog service. .IP "\fBPrepended" Messages that triggered header_checks or body_checks PREPEND action. .IP "\fBProcessExit" Postfix services that exited unexpectedly. .IP "\fBProcessLimit" A Postfix service has reached or exceeded the maximum number of processes allowed. .IP "\fBQueueWriteError" Problems writing a Postfix queue file. .IP "\fBRblError" Lookup errors for RBLs. .IP "\fBRedirected" Messages that triggered access, header_checks or body_checks REDIRECT action. .IP "\fB###RejectBody" Messages that triggered body_checks REJECT action. .IP "\fB###RejectClient" Messages rejected by client access controls (smtpd_client_restrictions). .IP "\fB###RejectConfigError" Message rejected due to server configuration errors. .IP "\fB###RejectContent" Messages rejected by message_reject_characters. .IP "\fB###RejectData" Messages rejected at DATA stage in SMTP conversation (smtpd_data_restrictions). .IP "\fB###RejectEtrn" Messages rejected at ETRN stage in SMTP conversation (smtpd_etrn_restrictions). .IP "\fB###RejectHeader" Messages that triggered header_checks REJECT action. .IP "\fB###RejectHelo" Messages rejected at HELO/EHLO stage in SMTP conversation (smtpd_helo_restrictions). .IP "\fB###RejectInsufficientSpace" Messages rejected due to insufficient storage space. .IP "\fB###RejectLookupFailure" Messages rejected due to temporary DNS lookup failures. .IP "\fB###RejectMilter" Milter rejects. No reject reply code is available for these rejects, but an extended 5.7.1 DSN is provided. These rejects are forced into the generic 5xx rejects group. If you redefine \fBreject_reply_patterns\fR such that it does not contain the pattern \fB5..\fR, milter rejects will not be output. .IP "\fB###RejectRbl" Messages rejected by an RBL hit. .IP "\fB###RejectRecip" Messages rejected by recipient access controls (smtpd_recipient_restrictions). .IP "\fB###RejectRelay" Messages rejected by relay access controls. .IP "\fB###RejectSender" Messages rejected by sender access controls (smtpd_sender_restrictions). .IP "\fB###RejectSize" Messages rejected due to excessive message size. .IP "\fB###RejectUnknownClient" Messages rejected by unknown client access controls. .IP "\fB###RejectUnknownReverseClient" Messages rejected by unknown reverse client access controls. .IP "\fB###RejectUnknownUser" Messages rejected by unknown user access controls. .IP "\fB###RejectUnverifiedClient" Messages rejected by unverified client access controls. .IP "\fB###RejectVerify" Messages rejected dueo to address verification failures. .IP "\fBReplaced" Messages that triggered header_checks or body_checks REPLACE action. .IP "\fBReturnedToSender" Messages returned to sender due to exceeding queue lifetime (maximal_queue_lifetime). .IP "\fBSaslAuth" SASL authentication successes, includes SASL method, username, and sender when present. .IP "\fBSaslAuthFail" SASL authentication failures. .IP "\fBSent" Messages sent via the SMTP delivery agent. .IP "\fBSentLmtp" Messages sent via the LMTP delivery agent. .IP "\fBSmtpConversationError" Errors during the SMTP/ESMTP dialog. .IP "\fBSmtpProtocolViolation" Protocol violation during the SMTP/ESMTP dialog. .IP "\fBStartupError" Errors during Postfix server startup. .IP "\fBTimeoutInbound" Connections to \fBsmtpd\fR that timed out. .IP "\fBTlsClientConnect" TLS client connections. .IP "\fBTlsOffered" TLS communication offered. .IP "\fBTlsServerConnect" TLS server connections. .IP "\fBTlsUnverified" Unverified TLS connections. .IP "\fBUndeliverable" Address verification indicates recipient address is undeliverable. .IP "\fBWarn" Messages that triggered access, header_checks or body_checks WARN action. .IP "\fBWarnConfigError" Warnings regarding Postfix configuration errors. .IP "\fBWarningsOther" Postfix general \fBwarning\fR messages. .PD .SH LEVEL CONTROL .ad .fi The \fBDetailed\fR section of the report consists of a number of sub-sections, each of which is controlled both globally and independently. Two settings influence the output provided in the \fBDetailed\fR report: a global detail level (specified with \fB--detail\fR) which has final (big hammer) output-limiting control over the \fBDetailed\fR section, and sub-section specific detail settings (small hammer), which allow further limiting of the output for a sub-section. Each sub-section may be limited to a specific depth level, and each sub-level may be limited with top N or threshold limits. The \fIlevelspec\fR argument to each of the level limiters listed above is used to accomplish this. It is probably best to continue explanation of sub-level limiting with the following well-known outline-style hierarchy, and some basic examples: .nf level 0 level 1 level 2 level 3 level 4 level 4 level 2 level 3 level 4 level 4 level 4 level 3 level 4 level 3 level 1 level 2 level 3 level 4 .fi .PP The simplest form of output limiting suppresses all output below a specified level. For example, a \fIlevelspec\fR set to "2" shows only data in levels 0 through 2. Think of this as collapsing each sub-level 2 item, thus hiding all inferior levels (3, 4, ...), to yield: .nf level 0 level 1 level 2 level 2 level 1 level 2 .fi .PP 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. Consider a dictionary spam attack, which produces very lengthy lists of hit-once recipient email or IP addresses. Each sub-level in the hierarchy can be threshold-limited by setting the \fIlevelspec\fR appropriately. Setting \fIlevelspec\fR to the value "2::5" will suppress any data at level 2 that does not exceed a hit count of 5. .PP Perhaps producing a top N list, such as top 10 senders, is desired. A \fIlevelspec\fR of "3:10:" limits level 3 data to only the top 10 hits. .PP 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: .IP "\fIl\fR" Specifies the maximum level to be output for this sub-section, with a range from 0 to 10. if \fIl\fR is 0, no levels will be output, effectively disabling the sub-section (level 0 data is already provided in the Summary report, so level 1 is considered the first useful level in the \fBDetailed\fR report). Higher values will produce output up to and including the specified level. .IP "\fIl\fB.\fIn\fR" Same as above, with the addition that \fIn\fR limits this section's level 1 output to the top \fIn\fR items. The value for \fIn\fR can be any integer greater than 1. (This form of limiting has less utility than the syntax shown below. It is provided for backwards compatibility; users are encouraged to use the syntax below). .IP "\fIl\fB:\fIn\fB:\fIt\fR" This triplet specifies level \fIl\fR, top \fIn\fR, and minimum threshold \fIt\fR. Each of the values are integers, with \fIl\fR being the level limiter as described above, \fIn\fR being a top \fIn\fR limiter for the level \fIl\fR, and \fIt\fR being the threshold limiter for level \fIl\fR. When both \fIn\fR and \fIt\fR are specified, \fIn\fR has priority, allowing top \fIn\fR lists (regardless of threshold value). If the value of \fIl\fR is omitted, the specified values for \fIn\fR and/or \fIt\fR are used for all levels available in the sub-section. This permits a simple form of wildcarding (eg. place minimum threshold limits on all levels). However, specific limiters always override wildcard limiters. The first form of level limiter may be included in \fIlevelspec\fR to restrict output, regardless of how many triplets are present. .PP All three forms of limiters are effective only when \fBpostfix-logwatch\fR's detail level is 5 or greater (the \fBDetailed\fR section is not activated until detail is at least 5). .PP See the \fBEXAMPLES\fR section for usage scenarios. .SH CONFIGURATION FILE .ad \fBPostfix-logwatch\fR can read configuration settings from a configuration file. Essentially, any command line option can be placed into a configuration file, and these settings are read upon startup. Because \fBpostfix-logwatch\fR can run either standalone or within Logwatch, to minimize confusion, \fBpostfix-logwatch\fR inherits Logwatch's configuration file syntax requirements and conventions. These are: .IP \(bu 4'. White space lines are ignored. .IP \(bu 4'. Lines beginning with \fB#\fR are ignored .IP \(bu 4'. Settings are of the form: .nf \fIoption\fB = \fIvalue\fR .fi .IP \(bu 4'. Spaces or tabs on either side of the \fB=\fR character are ignored. .IP \(bu 4'. Any \fIvalue\fR protected in double quotes will be case-preserved. .IP \(bu 4'. All other content is reduced to lowercase (non-preserving, case insensitive). .IP \(bu 4'. All \fBpostfix-logwatch\fR configuration settings must be prefixed with "\fB$postfix_\fR" or \fBpostfix-logwatch\fR will ignore them. .IP \(bu 4'. When running under Logwatch, any values not prefixed with "\fB$postfix_\fR" are consumed by Logwatch; it only passes to \fBpostfix-logwatch\fR (via environment variable) settings it considers valid. .IP \(bu 4'. The values \fBTrue\fR and \fBYes\fR are converted to 1, and \fBFalse\fR and \fBNo\fR are converted to 0. .IP \(bu 4'. Order of settings is not preserved within a configuration file (since settings are passed by Logwatch via environment variables, which have no defined order). .PP To include a command line option in a configuration file, prefix the command line option name with the word "\fB$postfix_\fR". The following configuration file setting and command line option are equivalent: .nf \fB$postfix_Line_Style = Truncate\fR \fB--line_style Truncate\fR .fi Level limiters are also prefixed with \fB$postfix_\fR, but on the command line are specified with the \fB--limit\fR option: .nf \fB$postfix_Sent = 2\fR \fB--limit Sent=2\fR .fi The order of command line options and configuration file processing occurs as follows: 1) The default configuration file is read if it exists and no \fB--config_file\fR was specified on a command line. 2) Configuration files are read and processed in the order found on the command line. 3) Command line options override any options already set either via command line or from any configuration file. Command line options are interpreted when they are seen on the command line, and later options will override previously set options. The notable exception is with limiter variables, which are interpreted in the order found, but only after all other options have been processed. This allows \fB--reject_reply_patterns\fR to determine the dynamic list of the various reject limiters. See also \fB--reject_reply_patterns\fR. .SH "EXIT STATUS" .na .nf .ad .fi The \fBpostfix-logwatch\fR utility exits with a status code of 0, unless an error occurred, in which case a non-zero exit status is returned. .SH "EXAMPLES" .na .nf .ad .fi .SS Running Standalone \fBNote:\fR \fBpostfix-logwatch\fR reads its log data from one or more named Postfix log files, or from STDIN. For brevity, where required, the examples below use the word \fIfile\fR as the command line argument meaning \fI/path/to/postfix.log\fR. Obviously you will need to substitute \fIfile\fR with the appropriate path. .nf .PP To run \fBpostfix-logwatch\fR in standalone mode, simply run: .nf .RS 4 .PP \fBpostfix-logwatch \fIfile\fR .RE 0 .nf .PP A complete list of options and basic usage is available via: .nf .RS 4 .PP \fBpostfix-logwatch --help\fR .RE 0 .nf .PP To print a summary only report of Postfix log data: .nf .RS 4 .PP \fBpostfix-logwatch --detail 1 \fIfile\fR .RE 0 .fi .PP To produce a summary report and a one-level detail report for May 25th: .nf .RS 4 .PP \fBgrep 'May 25' \fIfile\fB | postfix-logwatch --detail 5\fR .RE 0 .fi .PP To produce only a top 10 list of Sent email domains, the summary report and detailed reports are first disabled. Since commands line options are read and enabled left-to-right, the Sent section is re-enabled to level 1 with a level 1 top 10 limiter: .nf .RS 4 .PP \fBpostfix-logwatch --nosummary --nodetail --limit sent='1 1:10:' \fIfile\fR .RE 0 .fi .PP The following command and its sample output shows a more complex level limiter example. The command gives the top 3 Sent email addresses from the top 5 domains, in addition, all level 3 items with a hit count of 2 or less are suppressed (in the Sent sub-section, this happens to be email's Original To address). Ellipses indicate top N or threshold-limited data: .nf .RS 4 .PP \fBpostfix-logwatch --nosummary --nodetail \\ --limit sent '1:5: 2:3: 3::2' \fIfile\fR .nf 1762 Sent via SMTP ----------------------------------- 352 example.com 310 joe 255 joe.bob@virtdomain.example.com 7 info@virtdomain.example.com 21 pooryoda3 11 hot93uh ... 244 sample.net 97 buzz 26 leroyjones 14 sally ... 152 example.net 40 jim_jameson 23 sam_sampson 19 paul_paulson ... 83 sample.us 44 root 39 jenny1 69 dom3.example.us 10 kay 7 ron 6 mrsmith ... ... .fi .RE 0 .fi .PP The next command uses both \fBreject_reply_patterns\fR and level limiters to see 421 RBL rejects, threshold-limiting level 2 output to hits greater than 5 (level 2 in the Reject RBL sub-section is the client's IP address / hostname pair). This makes for a very nice RBL offenders list, shown in the sample output (note the use of the unambiguous, abbreviated command line option reject_reply_pat): .nf .RS 4 .PP \fBpostfix-logwatch --reject_reply_pat '421 4.. 5.. Warn' \\ --nosummary --nodetail --limit 421rejectrbl='2 2::5' \fIfile\fR .nf 300 421 Reject RBL --------------------------------------- 243 zen.spamhaus.org=127.0.0.2 106 10.0.0.129 129.0.0.example.com 41 192.168.10.70 hostx10.sample.net 40 192.168.42.39 hostz42.sample.net 15 10.1.1.152 dsl-10-1-1-152.example.us 14 10.10.10.122 mail122.sample.com 7 192.168.3.44 smalltime-spammer.example.com ... 48 zen.spamhaus.org=127.0.0.4 17 10.29.124.92 10-29-124-92.adsl-static.sample.us ... 8 zen.spamhaus.org=127.0.0.11 ... 1 zen.spamhaus.org=127.0.0.10 ... .fi .RE 4 .SS Running within Logwatch \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. Since version 7.3.6, STDOUT is the default output destination, and the \fB--print\fR option has been replaced 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. .PP To print a summary report for today's Postfix log data: .nf .RS 4 .PP \fBlogwatch --service postfix --range today --detail 1\fR .RE 0 .nf .PP To print a report for today's Postfix log data, with one level of detail in the \fBDetailed\fR section: .nf .RS 4 .PP \fBlogwatch --service postfix --range today --detail 5\fR .RE 0 .fi .PP To print a report for yesterday, with two levels of detail in the \fBDetailed\fR section: .nf .RS 4 .PP \fBlogwatch --service postfix --range yesterday --detail 6\fR .RE 0 .fi .PP To print a report from Dec 12th through Dec 14th, with four levels of detail in the \fBDetailed\fR section: .nf .RS 4 .PP \fBlogwatch --service postfix --range \\ 'between 12/12 and 12/14' --detail 8\fR .RE 0 .PP To print a report for today, with all levels of detail: .nf .RS 4 .PP \fBlogwatch --service postfix --range today --detail 10\fR .RE 0 .PP Same as above, but leaves long lines uncut: .nf .RS 4 .PP \fBlogwatch --service postfix --range today --detail 11\fR .RE 0 .SH "ENVIRONMENT" .na .nf .ad .fi The \fBpostfix-logwatch\fR program uses the following (automatically set) environment variables when running under Logwatch: .IP \fBLOGWATCH_DETAIL_LEVEL\fR This is the detail level specified with the Logwatch command line argument \fB--detail\fR or the \fBDetail\fR setting in the ...conf/services/postfix.conf configuration file. .IP \fBLOGWATCH_DEBUG\fR This is the debug level specified with the Logwatch command line argument \fB--debug\fR. .IP \fBpostfix_\fIxxx\fR The Logwatch program passes all settings \fBpostfix_\fIxxx\fR in the configuration file ...conf/services/postfix.conf to the \fBpostfix\fR filter (which is actually named .../scripts/services/postfix) via environment variable. .SH "FILES" .na .nf .SS Standalone mode .IP "/usr/local/bin/postfix-logwatch" The \fBpostfix-logwatch\fR program .IP "/usr/local/etc/postfix-logwatch.conf" The \fBpostfix-logwatch\fR configuration file in standalone mode .SS Logwatch mode .IP "/etc/logwatch/scripts/services/postfix" The Logwatch \fBpostfix\fR filter .IP "/etc/logwatch/conf/services/postfix.conf" The Logwatch \fBpostfix\fR filter configuration file .SH "SEE ALSO" .na .nf logwatch(8), system log analyzer and reporter .SH "README FILES" .na .ad .nf README, an overview of \fBpostfix-logwatch\fR Changes, the version change list history Bugs, a list of the current bugs or other inadequacies Makefile, the rudimentary installer LICENSE, the usage and redistribution licensing terms .SH "LICENSE" .na .nf .ad Covered under the included MIT/X-Consortium License: http://www.opensource.org/licenses/mit-license.php .SH "AUTHOR(S)" .na .nf Mike Cappella .fi The original \fBpostfix\fR Logwatch filter was written by Kenneth Porter, and has had many contributors over the years. They are entirely not responsible for any errors, problems or failures since the current author's hands have touched the source code. logwatch-7.14/logwatch.conf.50000644000175000017500000000175213412053437016310 0ustar logwatchlogwatch.\" Written by Ivana Varekova . .TH LOGWATCH.CONF 5 2010-02-10 "GNU" "Linux Programmer's Manual" .SH NAME logwatch.conf, ignore.conf, override.conf - logwatch configuration files .SH DESCRIPTION .BR logwatch.conf - ( .BR /etc/logwatch/conf/logwatch.conf ) - can contain the local configuration options. The list of valid settings and their default values are in .BR /usr/share/logwatch/default.conf/logwatch.conf. .BR ignore.conf - ( .BR /etc/logwatch/conf/ignore.conf ) is the list of regular expressions. The set of logs described by this set is ignored by logwatch. .BR override.conf - ( .BR /etc/logwatch/conf/override.conf ) contains the settings which overrides the standard configuration of specific log files or services. The syntax is the same as in log/service files. .SH FILES .I /etc/logwatch/conf/logwatch.conf .I /etc/logwatch/conf/ignore.conf .I /etc/logwatch/conf/override.conf .I /usr/share/logwatch/default.conf/logwatch.conf .SH "SEE ALSO" .BR logwatch (8) logwatch-7.14/LICENSE0000644000175000017500000000204415075123620014465 0ustar logwatchlogwatchCopyright (c) 2002-2021 Kirk Bauer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. logwatch-7.14/doc/0000755000175000017500000000000015075123620014225 5ustar logwatchlogwatchlogwatch-7.14/doc/xsds/0000755000175000017500000000000015075123620015206 5ustar logwatchlogwatchlogwatch-7.14/doc/xsds/scripts/0000755000175000017500000000000015075123620016675 5ustar logwatchlogwatchlogwatch-7.14/doc/xsds/scripts/logwatch.xsd0000644000175000017500000000434615075123620021234 0ustar logwatchlogwatch logwatch-7.14/doc/xsds/scripts/syslog-ng.xsd0000644000175000017500000001101215075123620021332 0ustar logwatchlogwatch logwatch-7.14/ignore.conf.50000644000175000017500000000003012762262164015755 0ustar logwatchlogwatch.so man5/logwatch.conf.5logwatch-7.14/logwatch.spec0000644000175000017500000001637315134311757016164 0ustar logwatchlogwatchSummary: Analyzes and Reports on system logs Name: logwatch Version: 7.14 Release: 1 License: MIT Group: Applications/System URL: https://sourceforge.net/projects/logwatch/ BuildArch: noarch Source0: https://sourceforge.net/projects/logwatch/files/%{name}-%{version}/%{name}-%{version}.tar.gz Requires: perl,grep,crontabs,perl-HTML-Parser BuildRoot: %{_tmppath}/%{name}-%{version}-%{release} %description Logwatch is a customizable, pluggable log-monitoring system. It will go through your logs for a given period of time and make a report in the areas that you wish with the detail that you wish. Easy to use - works right out of the package on many systems. %prep %setup -q %build %install install -m 0755 -d %{buildroot}%{_var}/cache/logwatch install -m 0755 -d %{buildroot}%{_sysconfdir}/logwatch/scripts install -m 0755 -d %{buildroot}%{_sysconfdir}/logwatch/scripts/services install -m 0755 -d %{buildroot}%{_sysconfdir}/logwatch/conf install -m 0755 -d %{buildroot}%{_sysconfdir}/logwatch/conf/logfiles install -m 0755 -d %{buildroot}%{_sysconfdir}/logwatch/conf/services install -m 0755 -d %{buildroot}%{_datadir}/logwatch/default.conf/logfiles install -m 0755 -d %{buildroot}%{_datadir}/logwatch/default.conf/services install -m 0755 -d %{buildroot}%{_datadir}/logwatch/default.conf/html install -m 0755 -d %{buildroot}%{_datadir}/logwatch/dist.conf/logfiles install -m 0755 -d %{buildroot}%{_datadir}/logwatch/dist.conf/services install -m 0755 -d %{buildroot}%{_datadir}/logwatch/scripts/services install -m 0755 -d %{buildroot}%{_datadir}/logwatch/scripts/shared install -m 0755 -d %{buildroot}%{_datadir}/logwatch/lib for i in scripts/logfiles/* ; do if [ $(ls $i | wc -l) -ne 0 ] ; then install -m 0755 -d %{buildroot}%{_datadir}/logwatch/$i install -m 0644 $i/* %{buildroot}%{_datadir}/logwatch/$i fi done install -m 0755 scripts/logwatch.pl %{buildroot}%{_datadir}/logwatch/scripts/logwatch.pl install -m 0644 scripts/services/* %{buildroot}%{_datadir}/logwatch/scripts/services install -m 0644 scripts/shared/* %{buildroot}%{_datadir}/logwatch/scripts/shared install -m 0644 lib/* %{buildroot}%{_datadir}/logwatch/lib install -m 0644 conf/*.conf %{buildroot}%{_datadir}/logwatch/default.conf install -m 0644 conf/logfiles/* %{buildroot}%{_datadir}/logwatch/default.conf/logfiles install -m 0644 conf/services/* %{buildroot}%{_datadir}/logwatch/default.conf/services install -m 0644 conf/html/* %{buildroot}%{_datadir}/logwatch/default.conf/html install -m 0755 -d %{buildroot}%{_mandir}/man1 install -m 0644 amavis-logwatch.1 %{buildroot}%{_mandir}/man1 install -m 0644 postfix-logwatch.1 %{buildroot}%{_mandir}/man1 install -m 0755 -d %{buildroot}%{_mandir}/man5 install -m 0644 logwatch.conf.5 %{buildroot}%{_mandir}/man5 ln -s %{_mandir}/man5/logwatch.conf.5 %{buildroot}%{_mandir}/man5/ignore.conf.5 ln -s %{_mandir}/man5/logwatch.conf.5 %{buildroot}%{_mandir}/man5/override.conf.5 install -m 0755 -d %{buildroot}%{_mandir}/man8 install -m 0644 logwatch.8 %{buildroot}%{_mandir}/man8 rm -f %{buildroot}%{_sysconfdir}/cron.daily/logwatch \ %{buildroot}%{_sbindir}/logwatch install -m 0755 -d %{buildroot}%{_sysconfdir}/cron.daily install -m 0755 scheduler/logwatch.cron %{buildroot}%{_sysconfdir}/cron.daily/0logwatch install -m 0755 -d %{buildroot}%{_sbindir} ln -s %{_datadir}/logwatch/scripts/logwatch.pl %{buildroot}%{_sbindir}/logwatch echo "###### REGULAR EXPRESSIONS IN THIS FILE WILL BE TRIMMED FROM REPORT OUTPUT #####" > %{buildroot}%{_sysconfdir}/logwatch/conf/ignore.conf echo "# Local configuration options go here (defaults are in %{_datadir}/logwatch/default.conf/logwatch.conf)" > %{buildroot}%{_sysconfdir}/logwatch/conf/logwatch.conf echo "# Configuration overrides for specific logfiles/services may be placed here." > %{buildroot}%{_sysconfdir}/logwatch/conf/override.conf %clean rm -rf %{buildroot} %post %pre %preun %postun %files %defattr(-,root,root) %license LICENSE %doc README HOWTO-Customize-LogWatch LICENSE %dir %{_var}/cache/logwatch %dir %{_sysconfdir}/logwatch %dir %{_sysconfdir}/logwatch/* %dir %{_sysconfdir}/logwatch/*/* %{_sbindir}/logwatch %{_datadir}/logwatch %doc %{_mandir}/man*/* %config(noreplace) %{_sysconfdir}/logwatch/conf/*.conf %config(noreplace) %{_sysconfdir}/cron.daily/0logwatch %changelog * Thu Jan 22 2026 Frank Crawford 7.14 - release 7.14 * Tue Jun 22 2025 Bjorn 7.13 * Wed Jan 22 2025 Bjorn 7.12 * Mon Jul 22 2024 Bjorn 7.11 * Mon Jan 22 2024 Bjorn 7.10 * Sat Jul 22 2023 Jason Pyeron 7.9-2 - corrected dates in changelog, not released * Sat Jul 22 2023 Jason Pyeron 7.9-1 - release 7.8, noarch on EL and cygwin * Thu Jan 26 2023 Bjorn 7.8-3 - Made noarch version for linux * Sun Jan 22 2023 Jason Pyeron 7.8-2 - add missing requires (impacting EL8) - add dist to release * Sun Jan 22 2023 Jason Pyeron 7.8-1 * Fri Jul 22 2022 Bjorn 7.7 * Sat Jan 22 2022 Jason Pyeron 7.6-1 * Fri Jul 23 2021 Bjorn 7.5.6-1 * Sat Jan 23 2021 Jason Pyeron 7.5.5-1 * Wed Jul 22 2020 Bjorn 7.5.4-1 * Wed Jan 22 2020 Bjorn 7.5.3-1 * Mon Jul 22 2019 Bjorn 7.5.2-1 - Copying LICENSE to doc dir again * Tue Jan 22 2019 Bjorn 7.5.1-1 * Fri Dec 28 2018 Bjorn 7.5.0 - simplified files section - using symbolic links for some man files * Tue Sep 23 2014 Stefan Jakobs 7.4.1-1 - Install other manpages, too * Fri Sep 15 2006 Kirk Bauer 7.3.1-1 - Fixed install script to create empty scripts directory in /etc * Sat Oct 08 2005 Kirk Bauer pre7.0-1 - Numerous changes, most notably a whole new directory structure. * Thu Feb 24 2005 Kirk Bauer 6.0.1-1 - Now includes ignore.conf in the RPM * Mon Nov 03 2003 Kirk Bauer pre5.0-1 - Now can build without change as non-root user * Thu Feb 27 2003 Erik Ogan 4.3.2 - Added libdir & lib/Logwatch.pm * Sun Oct 13 2002 Kirk Bauer pre4.0-14 - Changed the 'logwatch' cron.daily job to '0logwatch' to run before logrotate * Thu Oct 10 2002 Kirk Bauer pre4.0-1 - Cronjob is now just named logwatch and not 00-logwatch * Wed May 01 2002 Kirk Bauer 3.0-6 - up2date packaged... finally! * Wed May 01 2002 Kirk Bauer 3.0-5 - Hopefully now properly included the up2date filter! * Mon Apr 29 2002 Kirk Bauer pre3.0-1 - Now properly includes logfile-specific scripts * Tue Apr 09 2002 Kirk Bauer 2.8-2 - Made man page entry in files list backwards compatible * Thu Mar 28 2002 Kirk Bauer 2.5-2 - Updated new changes from Red Hat's rawhide packaging * Wed Nov 18 1998 Kirk Bauer - Modified to comply with RHCN standards * Mon Feb 23 1998 Kirk Bauer - Minor changes and addition of man-page * Sun Feb 22 1998 Kirk Bauer - initial release logwatch-7.14/logwatch.80000644000175000017500000001321715134311335015363 0ustar logwatchlogwatch.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH LOGWATCH 8 "May 2012" Linux "User Manuals" .SH NAME logwatch \- system log analyzer and reporter .SH SYNOPSIS .B logwatch [--detail .I level .B ] [--logfile .I log-file-group .B ] [--service .I service-name .B ] [--mailto .I address .B ] [--noarchives] [--range .I range .B ] [--debug .I level .B ] [--filename .I file-name .B ] [--logdir .I directory .B ] [--hostlimit .I hosts .B ] [--hostname .I hostname .B ] [--html_wrap .I number of characters .B ] [--hostformat .I host based options .B ] [--output .I output-type .B ] [--format .I report format .B ] [--subject .I email subject .B ] [--encode .I encoding to use .B ] [--numeric] [--version] [--help|--usage] .SH DESCRIPTION .B Logwatch is a customizable, pluggable log-monitoring system. It will go through your logs for a given period of time and make a report in the areas that you wish with the detail that you wish. Logwatch is being used for Linux and many types of UNIX. .SH OPTIONS .IP "\fB--detail\fR level" This is the detail level of the report. .I level can be a positive integer, or high, med, low, which correspond to the integers 10, 5, and 0, respectively. .IP "\fB--logfile\fR log-file-group" This will force Logwatch to process only the set of logfiles defined by .I log-file-group (i.e. messages, xferlog, ...). Logwatch will therefore process all services that use those logfiles. This option can be specified more than once to specify multiple logfile-groups. .IP "\fB--service\fR service-name" This will force Logwatch to process only the service specified in .I service-name (i.e. login, pam, identd, ...). Logwatch will therefore also process any log-file-groups necessary to process these services. This option can be specified more than once to specify multiple services to process. A useful .I service-name is .I All which will process all services (and logfile-groups) for which you have filters installed. .IP "\fB--mailto\fR address" Mail the results to the email address or user specified in .I address. .IP "\fB--range\fR range" You can specify a date-range to process. Common ranges are .I Yesterday, Today, All, and .I Help. Additional options are listed when invoked with the .I Help parameter. .IP "\fB--noarchives\fR" Each log-file-group has basic logfiles (i.e. /var/log/messages) as well as archives (i.e. /var/log/messages.? or /var/log/messages.?.gz). By default, Logwatch will search through the archives in addition to the regular logfiles. For values of \-\-range other than "all", Logwatch will search the appropriate archived logs. If "\-\-noarchives" is specified, Logwatch will not search the archived logs for any value of "\-\-range" .IP "\fB--debug\fR level" For debugging purposes. .I level typically ranges in value from 0 to 100. This will clutter up your output, so you probably only want to use this when debugging. Values of 100 or more will retain the temporary files (by default, created under /var/cache/logwatch, unless specified as tmpdir in the logwatch.conf file). .IP "\fB--filename\fR file-name" Save the output to .I file-name instead of displaying or mailing it. .IP "\fB--logdir\fR directory" Look in .I directory for log subdirectories or log files first before looking in the default directories. .IP "\fB--hostlimit\fR host1,host2" Limit report to hostname - host1, host2. .IP "\fB--hostname\fR hostname" Use .I hostname for the reports instead of this system's hostname. In addition, if HostLimit is set in the logwatch.conf configuration file (see \fBMORE INFORMATION\fR, below), then only logs from this hostname will be processed (where appropriate). .IP "\fB--html_wrap\fR num-characters" Number of characters that html output should be wrapped to. Default is 80. .IP "\fB--hostformat\fR split" Use .I split approach when formatting report for multiple hosts - none [default], split, splitmail. .IP "\fB--output\fR output-type" Report using .I output-type - stdout [default], mail, file. .IP "\fB--format\fR format" Format report using .I format - text [default], html. .IP "\fB--subject\fR email-subject" Customize the email subject sent by Logwatch. Option \fB--output\fR must be set to mail. .IP "\fB--encode\fR encoding" Encode report using .I encoding - none [default], base64, 7bit, 8bit [same as 'none']. .IP "\fB--numeric\fR" Inhibits additional name lookups, displaying IP addresses numerically. .IP "\fB--usage\fR" Displays usage information .IP "\fB--help\fR" same as \-\-usage. .SH FILES .IP /usr/share/logwatch/ .RS This directory contains all the perl executables and configuration files shipped with the logwatch distribution. .RE .IP /etc/logwatch .RS This directory contains local configuration files that override the default configuration. See \fBMORE INFORMATION\fR below for more information. .RE .SH EXAMPLES .B logwatch --service ftpd-xferlog --range all --detail high .RS This will print out all FTP transfers that are stored in all current and archived xferlogs. .RE .B logwatch --service pam_pwdb --range yesterday --detail high .RS This will print out login information for the previous day... .RE .SH MORE INFORMATION The directory /usr/share/doc/logwatch-* contains several files with additional documentation: .RE .I HOWTO-Customize-LogWatch .RS Documents the directory structure of Logwatch configuration and executable files, and describes how to customize Logwatch by overriding these default files. .RE .I LICENSE .RS Describes the License under which Logwatch is distributed. Additional clauses may be specified in individual files. .RE .I README .RS Describes how to install, where to find it, mailing lists, and other useful information. .SH AUTHOR .RE Kirk Bauer .RE http://sourceforge.net/projects/logwatch logwatch-7.14/lib/0000755000175000017500000000000015075123620014226 5ustar logwatchlogwatchlogwatch-7.14/lib/Logwatch.pm0000644000175000017500000004307515075123620016345 0ustar logwatchlogwatch#!/usr/bin/perl # # $Id$ package Logwatch; use strict; use Exporter; use POSIX qw(strftime); =pod =head1 NAME Logwatch -- Utility functions for Logwatch Perl modules. =head1 SYNOPSIS use Logwatch ':sort'; ## ## Show CountOrder() ## # Sample Data my %UnknownUsers = (jb1o => 4, eo00 => 1, ma3d => 4, dr4b => 1); my $sortClosure = CountOrder(%UnknownUsers); foreach my $user (sort $sortClosure keys %UnknownUsers) { my $plural = ($UnknownUsers{$user} > 1) ? "s" : ""; printf " %-8s : %2d time%s\n", $user, $UnknownUsers{$user}, $plural; } ## ## Show TotalCountOrder() ## # Sample Data my %RelayDenied = ( some.server => {you@some.where => 2, foo@bar.com => 4}, other.server => { foo@bar.com => 14 } ); my $sub = TotalCountOrder(%RelayDenied); foreach my $relay (sort $sub keys %RelayDenied) { print " $relay:\n"; my $countOrder = CountOrder(%{$RelayDenied{$relay}}); foreach my $dest (sort $countOrder keys %{$RelayDenied{$relay}}) { my $plural = ($RelayDenied{$relay}{$dest} > 1) ? "s" : ""; printf " %-36s: %3d Time%s\n", $dest, $RelayDenied{$relay}{$dest}, $plural; } } use Logwatch ':ip'; ## ## Show SortIP() ## # Sample Data @ReverseFailures = qw{10.1.1.1 172.16.1.1 10.2.2.2 192.168.1.1 }; @ReverseFailures = sort SortIP @ReverseFailures; { local $" = "\n "; print "Reverse DNS Failures:\n @ReverseFailures\n" } -or- ## ## Show LookupIP() ## foreach my $ip (sort SortIP @ReverseFailures) { printf "%15s : %s\n", $ip, LookupIP($ip); } =head1 DESCRIPTION This module provides utility functions intended for authors of Logwatch scripts. The purpose is to abstract commonly performed actions into a set of generally available subroutines. The subroutines can optionally be imported into the local namespace. =over 4 =cut our @ISA = qw{Exporter}; our @EXPORT; our @EXPORT_OK; our %EXPORT_TAGS = (sort => [qw(CountOrder TotalCountOrder SortIP)], ip => [qw(DoLookup LookupIP SortIP)], dates => [qw(RangeHelpDM GetPeriod TimeBuild TimeFilter)], ); Exporter::export_ok_tags(qw{sort ip dates}); $EXPORT_TAGS{all} = [@EXPORT, @EXPORT_OK]; =pod =item I This function returns a closure suitable to be passed to Perl's C builtin. When two values are passed to the closure, it compares the numeric values of those keys in C<%hash>, and if they're equal, the lexically order of the keys. Thus: my $sortClosure = CountOrder(%UnknownUsers); foreach my $user (sort $sortClosure keys %UnknownUsers) { my $plural = ($UnknownUsers{$user} > 1) ? "s" : ""; printf " %-8s : %2d time%s\n", $user, $UnknownUsers{$user}, $plural; } Will print the keys and values of C<%UnknownUsers> in frequency order, with keys of equal values sorted lexically. The optional second argument is a coderef to be used to sort the keys in an order other than lexically. (a reference to C, for example.) =cut # Use a closure to abstract the sort algorithm sub CountOrder(\%;&) { my $href = shift; my $coderef = shift; return sub { # $a & $b are in the caller's namespace, moving this inside # guarantees that the namespace of the sort is used, in case # it's different (admittedly, that's highly unlikely), at a # miniscule performance cost. my $package = (caller)[0]; no strict 'refs'; # Back off, man. I'm a scientist. my $A = $ {"${package}::a"}; my $B = $ {"${package}::b"}; use strict 'refs'; # We are a hedge. Please move along. # Reverse the count, but not the compare my $count = $href->{$B} <=> $href->{$A}; return $count if $count; if (ref $coderef) { $a = $A; $b = $B; &$coderef(); } else { ($A cmp $B); } } } =pod =item I This function returns a closure similar to that returned by C, except that it assumes a hash of hashes, and totals the keys of each sub hash. Thus: my $sub = TotalCountOrder(%RelayDenied); foreach my $relay (sort $sub keys %RelayDenied) { print " $relay:\n"; my $countOrder = CountOrder(%{$RelayDenied{$relay}}); foreach my $dest (sort $countOrder keys %{$RelayDenied{$relay}}) { my $plural = ($RelayDenied{$relay}{$dest} > 1) ? "s" : ""; printf " %-36s: %3d Time%s\n", $dest, $RelayDenied{$relay}{$dest}, $plural; } } Will print the relays in the order of their total denied destinations (equal keys sort lexically), with each sub hash printed in frequency order (equal keys sorted lexically) The optional second argument is a coderef to be used to sort the keys in an order other than lexically. (a reference to C, for example.) =cut sub TotalCountOrder(\%;&) { my $href = shift; my $coderef = shift; my $cache = {}; return sub { # $a & $b are in the caller's namespace, moving this inside # guarantees that the namespace of the sort is used, in case # it's different (admittedly, that's highly unlikely), at a # miniscule performance cost. my $package = (caller)[0]; no strict 'refs'; # Back off, man. I'm a scientist. my $A = $ {"${package}::a"}; my $B = $ {"${package}::b"}; use strict 'refs'; # We are a hedge. Please move along. my ($AA, $BB); foreach my $tuple ( [\$A, \$AA], [\$B, \$BB] ) { my $keyRef = $tuple->[0]; my $totalRef = $tuple->[1]; if (exists($cache->{$$keyRef})) { $$totalRef = $cache->{$$keyRef}; } else { grep {$$totalRef += $href->{$$keyRef}->{$_}} keys %{$href->{$$keyRef}}; $cache->{$$keyRef} = $$totalRef; } } my $count = $BB <=> $AA; return $count if $count; if (ref $coderef) { $a = $A; $b = $B; &$coderef(); } else { ($A cmp $B); } } } =pod =item I This function is meant to be passed to the perl C builtin. It sorts a list of "dotted quad" IP addresses by the values of the individual octets. =cut sub canonical_ipv6_address { my @a = split /:/, shift; my @b = qw(0 0 0 0 0 0 0 0); my $i = 0; # comparison is numeric, so we use hex function while (defined $a[0] and $a[0] ne '') {$b[$i++] = hex(shift @a);} @a = reverse @a; $i = 7; while (defined $a[0] and $a[0] ne '') {$b[$i--] = hex(shift @a);} @b; } sub SortIP { # $a & $b are in the caller's namespace. my $package = (caller)[0]; no strict 'refs'; # Back off, man. I'm a scientist. my $A = $ {"${package}::a"}; my $B = $ {"${package}::b"}; $A =~ s/^::(ffff:)?(\d+\.\d+\.\d+\.\d+)$/$2/; $B =~ s/^::(ffff:)?(\d+\.\d+\.\d+\.\d+)$/$2/; use strict 'refs'; # We are a hedge. Please move along. if ($A =~ /:/ and $B =~ /:/) { my @a = canonical_ipv6_address($A); my @b = canonical_ipv6_address($B); while ($a[1] and $a[0] == $b[0]) {shift @a; shift @b;} $a[0] <=> $b[0]; } elsif ($A =~ /:/) { -1; } elsif ($B =~ /:/) { 1; } else { my ($a1, $a2, $a3, $a4) = split /\./, $A; my ($b1, $b2, $b3, $b4) = split /\./, $B; $a1 <=> $b1 || $a2 <=> $b2 || $a3 <=> $b3 || $a4 <=> $b4; } } =pod =item I Sets whether C does hostname lookups (or not). Defaults to true. =item I This function performs a hostname lookup on a passed in IP address. It returns the IP (with the hostname in parentheses) on success and the IP address on failure. Results are cached, so that many calls with the same argument don't tax the resolver resources. For (new) backward compatibility, this function now uses the $DoLookup variable in the caller's namespace to determine if lookups will be made. =cut # Default to false my $DoLookup = 0; sub DoLookup { $DoLookup = shift; return; } # Might as well cache it for the duration of the run my %LookupCache = (); sub LookupIP { my $Addr = $_[0]; if ($ENV{'LOGWATCH_NUMERIC'} == 1 ) { return $Addr; } # "Socket" is used solely to get the AF_INET() and AF_INET6() # constants, usually 2 and 10, respectively. Using Socket is # preferred because of portability, and should be in the standard # Perl distribution. eval "use Socket"; my $hasSocket = $@? 0 : 1; return $Addr unless($DoLookup && $hasSocket); return $LookupCache{$Addr} if exists ($LookupCache{$Addr}); $Addr =~ s/^::ffff://; my $PackedAddr; my $name = ""; # there are other module functions that do this more gracefully # (such as inet_pton), but we can't guarantee that they are available # in every system, so we use the built-in gethostbyaddr. if ($Addr =~ /^[\d\.]*$/) { $PackedAddr = pack('C4', split /\./,$Addr); $name = gethostbyaddr($PackedAddr,AF_INET()); } elsif ($Addr =~ /^[0-9a-zA-Z:]*/) { $PackedAddr = pack('n8', canonical_ipv6_address($Addr)); $name = gethostbyaddr($PackedAddr, AF_INET6()); } if ($name) { my $val = "$Addr ($name)"; $LookupCache{$Addr} = $val; return $val; } else { $LookupCache{$Addr} = $Addr; return ($Addr); } } =pod =item I This function merely prints out some information about --range to STDERR. =cut sub RangeHelpDM { eval "use Date::Manip"; my $hasDM = $@ ? 0 : 1; if ($hasDM) { print STDERR "\nThis system has the Date::Manip module loaded, and therefore you may use all\n"; print STDERR "of the valid --range parameters.\n"; } else { print STDERR "\nThis system does not have Date::Manip module loaded, and therefore\n"; print STDERR "the only valid --range parameters are \"yesterday\", \"today\", or \"all\".\n"; print STDERR "The Date::Manip module can be installed by using either of:\n"; print STDERR " apt-get install libdate-manip-perl (recommended on Debian)'\n"; print STDERR " cpan -i 'Date::Manip'\n"; print STDERR " perl -MCPAN -e 'install Date::Manip'\n"; print STDERR "\nFollowing is a description of the full capabilities available if\n"; print STDERR "Date::Manip is available.\n"; } print STDERR <<"EOT"; The format of the range option is: --range \"date_range [period]\" Parameter date_range (and optional period) must be enclosed in double quotes if it is more than one word. The default for date_range is \"yesterday\". Valid instances of date_range have one of the following formats: yesterday today all date1 between date1 and date2 since date1 For the above, date1 and date2 have values that can be parsed with the Date::Manip perl module. Valid instances of the optional parameter period have one of the following formats: for (that|this) (year|month|day|hour|minute|second) for those (years|months|days|hours|minutes|seconds) The period defines the resolution of the date match. The default is \"for that day\". Examples: --range today --range yesterday --range \"4 hours ago for that hour\" --range \"-3 days\" --range \"since 2 hours ago for those hours\" --range \"between -10 days and -2 days\" --range \"Apr 15, 2005\" --range \"first Monday in May\" --range \"between 4/23/2005 and 4/30/2005\" --range \"2005/05/03 10:24:17 for that second\" (The last entry might be used by someone debugging a log or filter.) A caution about efficiency: a range of \"yesterday for those hours\" will search for log entries for the last 24 hours, and is inefficient because it searches for individual matches for each hour. A range of \"yesterday\" will search for log entries for the previous day, and it searches for a single date match. EOT ; } =pod =item I This function returns the period, which is the part after the "for (those|that|this) " in a range =cut sub GetPeriod { my $range = lc $ENV{"LOGWATCH_DATE_RANGE"} || "yesterday"; my ($period) = ($range =~ /for\s+(?:those|that|this)\s+(year|month|day|hour|minute|second)s?\s*$/); if ($range eq 'all') { $period = 'all'; } unless ($period) { $period = "day"; } return($period); } =pod =item I This function returns an array of integers denoting time since the epoch (Jan. 1, 1970). Each entry represents a timestamp for the period that will that will need to be looked up to create the filter. =cut sub TimeBuild { my @time_t; my $time = time; eval "use Date::Manip"; my $hasDM = $@ ? 0 : 1; if ($hasDM) { eval 'Date_TimeZone();'; if ($@) { die "ERROR: Date::Manip unable to determine TimeZone.\n\nExecute the following command in a shell prompt:\n\tperldoc Date::Manip\nThe section titled TIMEZONES describes valid TimeZones\nand where they can be defined.\n"; } } my $range = lc $ENV{"LOGWATCH_DATE_RANGE"} || "yesterday"; my $period = GetPeriod; $range =~ s/for\s+(?:those|that|this)\s+((year|month|day|hour|minute|second)s?)\s*$//; my ($range1, $range2) = ($range =~ /^between\s+(.*)\s+and\s+(.*)\s*$/); if ($range =~ /^\s*since\s+/) { ($range1) = ($range =~ /\s*since\s+(.*)/); $range2 = "now"; } if ($range1 && $range2 && $hasDM) { # range between two dates specified my $date1 = ParseDate($range1); my $date2 = ParseDate($range2); if ($date1 && $date2) { if (Date_Cmp($date1, $date2) > 0) { # make sure date1 is earlier my $switch_date = $date1; $date1 = $date2; $date2 = $switch_date; } while (Date_Cmp($date1, $date2) < 0) { $time_t[++$#time_t] = UnixDate($date1, "%s"); $date1 = DateCalc($date1, "+1 $period"); } $time_t[++$#time_t] = UnixDate($date2, "%s"); } else { # $date1 or $date2 not valid # set to zero, which indicates it is not parsed $time_t[0] = 0; } } else { # either a single date or we don't have Date::Manip if ($range eq 'yesterday') { $time_t[0] = $time-86400; } elsif ($range eq 'today') { $time_t[0] = $time; } elsif ($range eq 'all') { # set arbitrarily to 1 $time_t[0] = 1; } elsif ($hasDM) { $time_t[0] = UnixDate($range, "%s") || 0; } else { $time_t[0] = 0; } } # this is an optimization when we use Date::Manip, and # the period is either 'month' or 'year'. It is intended # to reduce the number of archived logs searched. # We use the second day of month or year to account for # different timezones. if ($time_t[0] && $hasDM) { my $mod_date = ParseDateString("epoch $time_t[0]"); if ($period =~ /^month|year$/) { # set to beginning of month $mod_date =~ s/\d\d\d\d:\d\d:\d\d$/0200:00:00/; if ($period =~ /^year$/) { # set to beginning of year $mod_date =~ s/\d\d0100:00:00/010200:00:00/; } } $time_t[0] = UnixDate($mod_date, "%s"); } return(@time_t); } =pod =item I This function returns a regexp to filter by date/time =cut sub TimeFilter { my ($format) = $_[0]; my $SearchDate; my $range = lc $ENV{"LOGWATCH_DATE_RANGE"} || "yesterday"; my $debug = $ENV{"LOGWATCH_DEBUG"} || 0; my @time_t = TimeBuild(); # get period my $period = GetPeriod; if ($debug > 5) { print STDERR "\nTimeFilter: Period is $period\n"; } # we need the following bracketed section because of 'last' { if ($period eq 'second') {last;} $format =~ s/%S/../; if ($period eq 'minute') {last;} $format =~ s/%M/../; if ($period eq 'hour') {last;} $format =~ s/%H/../; if ($period eq 'day') {last;} $format =~ s/%a/.../; $format =~ s/%d/../; $format =~ s/%e/../; if ($period eq 'month') {last;} $format =~ s/%b/.../; $format =~ s/%m/../; if ($period eq 'year') {last;} $format =~ s/%y/../; $format =~ s/%Y/..../; } $SearchDate .= "("; for my $time (@time_t) { if ($time) { $SearchDate .= strftime($format, localtime($time)) . "|"; } else { # the following is a string guaranteed to not match $SearchDate .= "Range \"$range\" not understood. "; print STDERR "ERROR: Range \"$range\" not understood\n"; RangeHelpDM; } } # get rid of last character (usually the extra "|") if (length($SearchDate) > 1) { chop($SearchDate); } $SearchDate .= ")"; if ($debug> 5) { # DebugSearchDate sometimes makes it more readable - not used # functionally my $DebugSearchDate = $SearchDate; $DebugSearchDate =~ tr/:/ /; $DebugSearchDate =~ tr/\./ /; $DebugSearchDate =~ tr/ //s; print STDERR "\nTimeFilter: SearchDate is $SearchDate\n"; print STDERR "\nTimeFilter: Debug SearchDate is $DebugSearchDate\n"; } return ($SearchDate); } =back =head1 TAGS In addition to importing each function name explicitly, the following tags can be used. =over 4 =item I<:sort> Imports C, C =item I<:ip> Imports C, C, and C =item I<:dates> Imports C =item I<:all> Imports all importable symbols. =cut 1; # vi: shiftwidth=3 tabstop=3 et logwatch-7.14/install_logwatch.sh0000644000175000017500000002630015075123620017353 0ustar logwatchlogwatch#!/bin/sh # #The MIT License # #Copyright (c) 2005-2006 Mike Tremaine # #Permission is hereby granted, free of charge, to any person obtaining #a copy of this software and associated documentation files (the "Software"), #to deal in the Software without restriction, including without limitation #the rights to use, copy, modify, merge, publish, distribute, sublicense, #and/or sell copies of the Software, and to permit persons to whom the #Software is furnished to do so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included #in all copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, #DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR #OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR #THE USE OR OTHER DEALINGS IN THE SOFTWARE. # __________________________________________________________________ # # File: install_logwatch.sh # Author: Mike Tremaine [mgt /at/ stellarcore.net] # Maintainer: Mike Tremaine [mgt /at/ stellarcore.net] # $Id$ # # $Log: install_logwatch.sh,v $ # Revision 1.20 2008/05/12 22:53:28 mike # removed -T flag no sure why it was there -mgt # # Revision 1.19 2008/05/08 23:15:55 mike # Added install to logwatch.cron if no cron.daily dir. -mgt # # Revision 1.18 2008/05/08 22:47:41 mike # Added BIGGER note for the new cron requirement -mgt # # Revision 1.17 2007/11/28 16:06:56 mike # Prefix option code from Craig Ruff -mgt # # Revision 1.16 2007/11/28 15:50:43 mike # Typod Craig Ruff sorry -mgt # # Revision 1.15 2007/11/28 15:49:47 mike # Patch from Craig Ruff for MANDIR under Solaris -mgt # # Revision 1.14 2007/11/28 15:43:14 mike # Patch from Craig Ruff for CONFIGDIR munge -mgt # # __________________________________________________________________ # #Note: This script is provided for the non-RPM installs. #It is preferred that logwatch be packaged by a distribution #specifically for your installation. But since that is not always #possible we have included this script. #Add PATHS for various OS options #Set PATH for solaris /usr/ucb/install PATH=/usr/ucb:$PATH #Set PATH for OpenBSD makewhatis /usr/libexec/makewhatis PATH=$PATH:/usr/libexec #Set PATH for IRIX makewhatis /usr/lib/makewhatis PATH=$PATH:/usr/lib export PATH #Set OS and GLOBIGNORE OS=`uname -s` GLOBIGNORE=*CVS #All these can be set via user input #Defaults BASEDIR="/usr/share/logwatch" CONFIGDIR="/etc/logwatch" TEMPDIR="/var/cache/logwatch" PERLEXE="/usr/bin/perl" MANDIR="/usr/share/man" #Command line options section ac_prev= systemd=0 for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval "$ac_prev=\$ac_option" ac_prev= continue fi ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'` case $ac_option in -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; --systemd) systemd=1;; *) echo "Unknown option '$ac_option'" echo "Valid options are --prefix and --systemd." echo " Option --prefix takes one argument: a path name, which will" echo " be prepended to installation directory names" echo " Option --systemd installs the systemd files logwatch.service" echo " and logwatch.timer rather than the default 0logwatch" echo " file in /etc/cron.daily/" exit ;; esac done if [ "x$prefix" != "x" ]; then BASEDIR=$prefix munge_base=1 CONFIGDIR=$BASEDIR/etc munge_conf=1 TEMPDIR=$BASEDIR/tmp munge_temp=1 MANDIR=$BASEDIR/man fi #Talk to user printf "#################################\n" printf "Preparing to install Logwatch\n" printf "Enter the path to the Logwatch BaseDir [$BASEDIR] : " read base if [ "$base" = "" ]; then printf "### Using $BASEDIR\n" else BASEDIR="$base" #Set munge flag munge_base=1 printf "### Using $BASEDIR [will modify logwatch.pl]\n" fi printf "Enter the path for the Logwatch ConfigDir [$CONFIGDIR] : " read config if [ "$config" = "" ]; then printf "### Using $CONFIGDIR\n" else CONFIGDIR="$config" munge_conf=1 printf "### Using $CONFIGDIR [will modify logwatch.pl]\n" fi printf "Enter the dir name to be used for temp files [$TEMPDIR] : " read temp if [ "$temp" = "" ]; then printf "### Using $TEMPDIR\n" else TEMPDIR="$temp" munge_temp=1 printf "### Using $TEMPDIR [will write to $CONFIGDIR/conf/logwatch.conf]\n" fi printf "Enter the location of perl [$PERLEXE] : " read perlexe if [ "$perlexe" = "" ]; then printf "### Using $PERLEXE\n" else PERLEXE="$perlexe" munge_perl=1 printf "### Using $PERLEXE [will modify logwatch.pl]\n" fi printf "Enter the dir name to used for the manpage [$MANDIR] : " read mandir if [ "$mandir" = "" ]; then printf "### Using $MANDIR\n" else MANDIR="$mandir" printf "### Using $MANDIR [Will try to run makewhatis]\n" fi echo "### Installing" #OS Tests for known issues if [ $OS = "Darwin" ]; then munge_gzcat=1 fi #Install is borked under IRIX #BASE install -m 0755 -d $BASEDIR install -m 0755 -d $BASEDIR/dist.conf install -m 0755 -d $BASEDIR/dist.conf/logfiles install -m 0755 -d $BASEDIR/dist.conf/services install -m 0755 -d $BASEDIR/default.conf install -m 0755 -d $BASEDIR/default.conf/logfiles install -m 0755 -d $BASEDIR/default.conf/services install -m 0755 -d $BASEDIR/default.conf/html install -m 0755 -d $BASEDIR/scripts install -m 0755 -d $BASEDIR/scripts/logfiles install -m 0755 -d $BASEDIR/scripts/services install -m 0755 -d $BASEDIR/scripts/shared install -m 0755 -d $BASEDIR/lib install -m 0644 README $BASEDIR/README install -m 0644 HOWTO-Customize-LogWatch $BASEDIR/HOWTO-Customize-LogWatch install -m 0644 conf/*.conf $BASEDIR/default.conf install -m 0644 conf/logfiles/* $BASEDIR/default.conf/logfiles install -m 0644 conf/services/* $BASEDIR/default.conf/services install -m 0644 conf/html/* $BASEDIR/default.conf/html install -m 0755 scripts/logwatch.pl $BASEDIR/scripts/logwatch.pl for i in scripts/logfiles/* ; do if [ `ls $i | grep -v CVS | wc -l` -ne 0 ] ; then install -m 0755 -d $BASEDIR/$i install -m 0644 $i/* $BASEDIR/$i fi done install -m 0644 scripts/shared/* $BASEDIR/scripts/shared install -m 0644 scripts/services/* $BASEDIR/scripts/services install -m 0644 lib/* $BASEDIR/lib if [ $munge_base ]; then perl -pi -e "s%/usr/share/logwatch%$BASEDIR%" $BASEDIR/scripts/logwatch.pl fi #CONFIG install -m 0755 -d $CONFIGDIR install -m 0755 -d $CONFIGDIR/scripts install -m 0755 -d $CONFIGDIR/scripts/services install -m 0755 -d $CONFIGDIR/conf install -m 0755 -d $CONFIGDIR/conf/logfiles install -m 0755 -d $CONFIGDIR/conf/services install -m 0755 -d $CONFIGDIR/conf/html if [ $munge_conf ]; then perl -pi -e "s%/etc/logwatch%$CONFIGDIR%" $BASEDIR/scripts/logwatch.pl fi touch $CONFIGDIR/conf/logwatch.conf touch $CONFIGDIR/conf/ignore.conf touch $CONFIGDIR/conf/override.conf #TEMP #Using sanity check incase someone uses /tmp. #The install would destroy the perms on /tmp if [ ! -d $TEMPDIR ]; then #Should this be 0700 -d $TEMPDIR ?? install -m 0755 -d $TEMPDIR fi #This can create duplicates need to grep first -mgt if [ $munge_temp ]; then echo "TmpDir = $TEMPDIR" >> $CONFIGDIR/conf/logwatch.conf fi #PERL if [ $munge_perl ]; then perl -pi -e "s%/usr/bin/perl%$PERLEXE%" $BASEDIR/scripts/logwatch.pl fi #Gzcat if [ $munge_gzcat ]; then echo "Pathtozcat = gzcat" >> $CONFIGDIR/conf/logwatch.conf fi #Search for makewhatis for f in `echo $PATH | tr : ' '`; do if [ -x "$f/makewhatis" ]; then HAVE_MAKEWHATIS=1; fi; done #Man page if [ -d $MANDIR/man5 ] && [ -d $MANDIR/man8 ] && [ -d $MANDIR/man1 ] && [ $HAVE_MAKEWHATIS ]; then install -m 0644 logwatch.8 $MANDIR/man8 install -m 0644 logwatch.conf.5 $MANDIR/man5 ln -sf $MANDIR/man5/logwatch.conf.5 $MANDIR/man5/ignore.conf.5 ln -sf $MANDIR/man5/logwatch.conf.5 $MANDIR/man5/override.conf.5 install -m 0644 postfix-logwatch.1 $MANDIR/man1 install -m 0644 amavis-logwatch.1 $MANDIR/man1 #OpenBSD no -s if [ $OS = "OpenBSD" ]; then makewhatis -u $MANDIR/man5 $MANDIR/man8 $MANDIR/man1 else #FreeBSD and NetBSD no -s no -u if [ $OS = "FreeBSD" ] || [ $OS = "NetBSD" ]; then makewhatis $MANDIR/man5 $MANDIR/man8 $MANDIR/man1 else #MacOS X aka Darwin no -u [even thought the manpage says] if [ $OS = "Darwin" ]; then makewhatis -o "1 5 8" $MANDIR else #Linux makewhatis -u -s "1 5 8" $MANDIR fi fi fi else if [ $OS = "SunOS" ]; then #Go for the safe install rather than editing man.cf mkdir -p $MANDIR/man1m > /dev/null 2>&1 install -m 0644 logwatch.8 $MANDIR/man1m install -m 0644 logwatch.conf.5 $MANDIR/man1m ln -sf $MANDIR/man1m/logwatch.conf.5 $MANDIR/man1m/ignore.conf.5 ln -sf $MANDIR/man1m/logwatch.conf.5 $MANDIR/man1m/override.conf.5 install -m 0644 postfix-logwatch.1 $MANDIR/man1 install -m 0644 amavis-logwatch.1 $MANDIR/man1 catman -w -M $MANDIR/man1m else install -m 0755 -d $MANDIR/man1 install -m 0644 postfix-logwatch.1 $MANDIR/man1 install -m 0644 amavis-logwatch.1 $MANDIR/man1 install -m 0755 -d $MANDIR/man5 install -m 0644 logwatch.conf.5 $MANDIR/man5 ln -sf $MANDIR/man5/logwatch.conf.5 $MANDIR/man5/ignore.conf.5 ln -sf $MANDIR/man5/logwatch.conf.5 $MANDIR/man5/override.conf.5 install -m 0755 -d $MANDIR/man8 install -m 0644 logwatch.8 $MANDIR/man8 printf "Installed manpages in $MANDIR/man1, $MANDIR/man5 and $MANDIR/man8.\n" printf "Check your man.cf or man.conf to enable MANSECTS 1, 5 and 8\n" fi fi #Symlink ln -f -s $BASEDIR/scripts/logwatch.pl /usr/sbin/logwatch printf "Created symlink for /usr/sbin/logwatch \n" #Cron or Systemd timer if [ $systemd -eq 1 ]; then install -m 0644 scheduler/logwatch.service /lib/systemd/system/logwatch.service install -m 0644 scheduler/logwatch.timer /lib/systemd/system/logwatch.timer install -m 0644 scheduler/systemd.conf $BASEDIR/default.conf/systemd.conf if [ ! -e /lib/systemd/system/multi-user.target.wants ]; then install -m 0755 -d /lib/systemd/system/multi-user.target.wants fi ln -sf ../logwatch.timer /lib/systemd/system/multi-user.target.wants/logwatch.timer printf "Created and enabled systemd logwatch.timer" elif [ -d /etc/cron.daily ]; then rm -f /etc/cron.daily/0logwatch install -m 0755 scheduler/logwatch.cron /etc/cron.daily/0logwatch printf "Created /etc/cron.daily/0logwatch \n" else install -m 0744 scheduler/logwatch.cron $CONFIGDIR/logwatch.cron printf "################ README ####################.\n" printf "You need to setup your cron job for logwatch.\n" printf "A sample script is included see $CONFIGDIR/logwatch.cron. \n" printf "2 0 * * * $CONFIGDIR/logwatch.cron >/dev/null 2>&1 \n" fi exit # vi: shiftwidth=3 tabstop=3 et logwatch-7.14/scripts/0000755000175000017500000000000015134312032015140 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/0000755000175000017500000000000012762262164016762 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/yum/0000755000175000017500000000000015075123620017565 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/yum/applydate0000644000175000017500000000336515075123620021502 0ustar logwatchlogwatch########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(strftime); use Logwatch ':dates'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $time = time; # Two formats in use: SearchNewDate is for yum 2.1 and later, I believe my $SearchDate = TimeFilter('%m/%d/%y %H:%M:%S'); my $SearchNewDate = TimeFilter('%b %d %H:%M:%S'); if ( $Debug > 5 ) { print STDERR "DEBUG: Inside ApplyDate (yum)...\n"; print STDERR "DEBUG: Looking For: " . $SearchDate . " or " . $SearchNewDate . "\n"; } while (defined(my $ThisLine = )) { # Here we actually remove the dates, as well if ($ThisLine =~ s/^$SearchNewDate //o || $ThisLine =~ s/$SearchDate //o) { print $ThisLine; } } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/emerge/0000755000175000017500000000000012762262164020226 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/emerge/applydate0000644000175000017500000000543712762262164022145 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## # $Log: applydate,v $ # Revision 1.5 2008/03/24 23:31:26 kirk # added copyright/license notice to each script # # Revision 1.4 2007/02/16 04:38:13 bjorn # Check timestamp using proper "seconds since epoch" format, by Jason. # # Revision 1.3 2005/06/18 19:36:32 bjorn # Bug fix from Mike Frysinger for incorrect variable reference # # Revision 1.2 2005/05/03 19:33:39 bjorn # Added support for new date ranges # # Revision 1.1 2005/04/20 22:13:32 bjorn # Initial file by Matt Brown # ########################################################################## ########################################################################## # This was written by: Matt Brown, mdbrown at uwaterloo dot ca # # Please send all comments, suggestions, bug reports, # etc, to logwatch-devel@lists.sourceforge.net. ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### # Processes emerge logs to remove entries outside the desired date range use strict; use Logwatch ':dates'; use POSIX qw(strftime); my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; # Set the date we are looking for based on the desired date range my $SearchDate = TimeFilter('%Y %b %e %H:%M:%S'); if ($Debug > 5) { print STDERR "DEBUG: Inside applydate (emerge)...\n"; print STDERR "DEBUG: Looking For: $SearchDate\n"; } # Examine each line of the file, writing out only the lines that are within # the date range my $emergeTime; while (defined(my $line = )) { $line =~ /^(\d+):/; $emergeTime = strftime('%Y %b %e %H:%M:%S', localtime($1)); if ($Debug > 5) { print STDERR "DEBUG: converted time: $emergeTime\n"; } if ($emergeTime =~ /$SearchDate/) { print $line; } } # vi: shiftwidth=3 syntax=perl tabstop=3 et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/cron/0000755000175000017500000000000015075123620017714 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/cron/applydate0000755000175000017500000000473515075123620021636 0ustar logwatchlogwatch #use strict; ########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(strftime); use Logwatch ':dates'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; # SearchDate2 is for newer crond (i.e. RH7.X) my ($SearchDate, $SearchDate2, $ThisLine); my ($incount, $outcount) = (0, 0); my $time = time; my $hostname = $ENV{'HOSTNAME'}; my $OSname = $ENV{'OSname'}; $SearchDate = TimeFilter("%m/%d-%H:%M:%S"); $SearchDate2 = TimeFilter("%b %e %H:%M:%S"); if ($Debug > 5) { print STDERR "DEBUG: Inside ApplyDate (cron)...\n"; print STDERR "DEBUG: Looking For: $SearchDate or $SearchDate2\n"; } while (defined($ThisLine = )) { $incount++; #Solaris & IRIX CRON filter -mgt #Basically takes the cron format in /var/cron/log and makes it look like syslog if ( $OSname =~ /(SunOS|IRIX)/ ) { if ($ThisLine =~ m/^\>\s+CMD: (.+)$/o) { my $command = $1; my $nextline = ; my ($user, $ps, $datestamp) = $nextline =~ /^\>\s+(\w+) (\d+) . \w\w\w (\w\w\w\s+\d+ \d\d:\d\d:\d\d)/; $ThisLine = "$datestamp $hostname CROND[$ps]: ($user) CMD ($command)\n"; } } if ($ThisLine =~ m/^[^ ]+ \($SearchDate-[0123456789]+\) /o) { print $ThisLine; $outcount++; } elsif ($ThisLine =~ m/^$SearchDate2 [^ ]+ [\w\/]+\[\d+\]:/o) { print $ThisLine; $outcount++; } } if ($Debug > 5) { print STDERR "DEBUG: ApplyDate (cron): $incount Lines In, $outcount Lines Out\n"; } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/up2date/0000755000175000017500000000000015075123620020317 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/up2date/applydate0000755000175000017500000000301415075123620022226 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(strftime); use Logwatch ':dates'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $time = time; my $SearchDate = TimeFilter('%a %b %e %H:%M:%S %Y'); if ( $Debug > 5 ) { print STDERR "DEBUG: Inside ApplyDate (up2date)...\n"; print STDERR "DEBUG: Looking For: " . $SearchDate . "\n"; } while (defined(my $ThisLine = )) { if ($ThisLine =~ m/\[$SearchDate\]/o) { print $ThisLine; } } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/up2date/removeheaders0000755000175000017500000000234515075123620023102 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; while (defined(my $ThisLine = )) { $ThisLine =~ s/^\[... ... .. ..:..:.. ....\] up2date //; print $ThisLine; } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/xferlog/0000755000175000017500000000000015075123620020421 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/xferlog/applydate0000755000175000017500000000301215075123620022326 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(strftime); use Logwatch ':dates'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $time = time; my $SearchDate = TimeFilter('%b %e %H:%M:%S %Y'); if ( $Debug > 5 ) { print STDERR "DEBUG: Inside ApplyDate (xferlog)...\n"; print STDERR "DEBUG: Looking For: " . $SearchDate . "\n"; } while (defined(my $ThisLine = )) { if ($ThisLine =~ m/^... $SearchDate/o) { print $ThisLine; } } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/xferlog/removeheaders0000755000175000017500000000234715075123620023206 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; while (defined(my $ThisLine = )) { $ThisLine =~ s/^... ... .. ..:..:.. .... [0123456789]+ //; print $ThisLine; } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/samba/0000755000175000017500000000000015075123620020036 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/samba/applydate0000755000175000017500000000446515075123620021760 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## # This was written and is maintained by: # Luuk de Boer # # Please send all comments, suggestions, bug reports, # etc, to logwatch-devel@lists.sourceforge.net ######################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(strftime); use Logwatch ':dates'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $time = time; my $SearchDate = TimeFilter('%m/%d/%y %H:%M:%S'); my $SearchDate2 = TimeFilter('%Y/%m/%d %H:%M:%S'); if ( $Debug > 5 ) { print STDERR "DEBUG: Inside ApplyDate (samba)...\n"; print STDERR "DEBUG: Looking For: $SearchDate or $SearchDate2\n"; } my $ThisLine = ; mainloop: while ($ThisLine) { if ($ThisLine =~ m/^$SearchDate /o) { print $ThisLine; $ThisLine = ; } elsif ($ThisLine =~ m/^\[$SearchDate2/o) { chomp($ThisLine); print $ThisLine; while ($ThisLine = ) { if ($ThisLine =~ m/^\[....\/..\/.. ..:..:../) { # Found next entry print "\n"; next mainloop; } else { chomp($ThisLine); print $ThisLine; } } print "\n"; } else { $ThisLine = ; } } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/samba/removeheaders0000755000175000017500000000303515075123620022616 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## # This was written and is maintained by: # Luuk de Boer # # Please send all comments, suggestions, bug reports, # etc, to kirk@kaybee.org. ######################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; while (defined(my $ThisLine = )) { $ThisLine =~ s/^..\/..\/.. ..:..:.. //; $ThisLine =~ s/^\[....\/..\/.. ..:..:...+?\]\s*//; print $ThisLine; } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/logfiles/autorpm/0000755000175000017500000000000015075123620020442 5ustar logwatchlogwatchlogwatch-7.14/scripts/logfiles/autorpm/applydate0000755000175000017500000000300615075123620022352 0ustar logwatchlogwatch ########################################################################## # $Id$ ########################################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(strftime); use Logwatch ':dates'; my $time = time; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $SearchDate = TimeFilter('%a %b %e %H:..:.. [^ ]+ %Y - '); if ($Debug > 5) { print STDERR "DEBUG: Inside ApplyDate (autorpm)...\n"; print STDERR "DEBUG: Looking For: $SearchDate\n"; } while (defined(my $ThisLine = )) { if ($ThisLine =~ s/$SearchDate//o) { print $ThisLine; } } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/0000755000175000017500000000000015122643145016774 5ustar logwatchlogwatchlogwatch-7.14/scripts/services/amavis0000644000175000017500000053661315075123620020213 0ustar logwatchlogwatch#!/usr/bin/perl ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ### All work since Dec 12, 2006 (logwatch CVS revision 1.28) ### Copyright (c) 2006-2012 Mike Cappella ### ### Covered under the included MIT/X-Consortium License: ### http://www.opensource.org/licenses/mit-license.php ### All modifications and contributions by other persons to ### this script are assumed to have been donated to the ### Logwatch project and thus assume the above copyright ### and licensing terms. If you want to make contributions ### under your own copyright or a different license this ### must be explicitly stated in the contribution an the ### Logwatch project reserves the right to not accept such ### contributions. If you have made significant ### contributions to this script and want to claim ### copyright please contact logwatch-devel@lists.sourceforge.net. ########################################################## ########################################################################## # The original amavis logwatch filter was written by # Jim O'Halloran , and has had many contributors over # the years. # # CVS log removed: see Changes file for amavis-logwatch at # http://logreporters.sourceforge.net/ # or included with the standalone amavis-logwatch distribution ########################################################################## package Logreporters; use 5.008; use strict; use warnings; no warnings "uninitialized"; use re 'taint'; our $Version = '1.51.03'; our $progname_prefix = 'amavis'; # Specifies the default configuration file for use in standalone mode. my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf"; #MODULE: ../Logreporters/Utils.pm package Logreporters::Utils; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.003'; @ISA = qw(Exporter); @EXPORT = qw(&formathost &get_percentiles &get_percentiles2 &get_frequencies &commify &unitize &get_usable_sectvars &add_section &begin_section_group &end_section_group &get_version &unique_list); @EXPORT_OK = qw(&gen_test_log); } use subs qw (@EXPORT @EXPORT_OK); # Formats IP and hostname for even column spacing # sub formathost($ $) { # $_[0] : hostip # $_[1] : hostname; if (! $Logreporters::Config::Opts{'unknown'} and $_[1] eq 'unknown') { return $_[0]; } return sprintf "%-$Logreporters::Config::Opts{'ipaddr_width'}s %s", $_[0] eq '' ? '*unknown' : $_[0], $_[1] eq '' ? '*unknown' : lc $_[1]; } # Add a new section to the end of a section table # sub add_section($$$$$;$) { my $sref = shift; die "Improperly specified Section entry: $_[0]" if !defined $_[3]; my $entry = { CLASS => 'DATA', NAME => $_[0], DETAIL => $_[1], FMT => $_[2], TITLE => $_[3], }; $entry->{'DIVISOR'} = $_[4] if defined $_[4]; push @$sref, $entry; } { my $group_level = 0; # Begin a new section group. Groups can nest. # sub begin_section_group($;@) { my $sref = shift; my $group_name = shift; my $entry = { CLASS => 'GROUP_BEGIN', NAME => $group_name, LEVEL => ++$group_level, HEADERS => [ @_ ], }; push @$sref, $entry; } # Ends a section group. # sub end_section_group($;@) { my $sref = shift; my $group_name = shift; my $entry = { CLASS => 'GROUP_END', NAME => $group_name, LEVEL => --$group_level, FOOTERS => [ @_ ], }; push @$sref, $entry; } } # Generate and return a list of section table entries or # limiter key names, skipping any formatting entries. # If 'namesonly' is set, limiter key names are returned, # otherwise an array of section array records is returned. sub get_usable_sectvars(\@ $) { my ($sectref,$namesonly) = @_; my (@sect_list, %unique_names); foreach my $sref (@$sectref) { #print "get_usable_sectvars: $sref->{NAME}\n"; next unless $sref->{CLASS} eq 'DATA'; if ($namesonly) { $unique_names{$sref->{NAME}} = 1; } else { push @sect_list, $sref; } } # return list of unique names if ($namesonly) { return keys %unique_names; } return @sect_list; } # Print program and version info, preceeded by an optional string, and exit. # sub get_version() { print STDOUT "@_\n" if ($_[0]); print STDOUT "$Logreporters::progname: $Logreporters::Version\n"; exit 0; } # Returns a list of percentile values given a # sorted array of numeric values. Uses the formula: # # r = 1 + (p(n-1)/100) = i + d (Excel method) # # r = rank # p = desired percentile # n = number of items # i = integer part # d = decimal part # # Arg1 is an array ref to the sorted series # Arg2 is a list of percentiles to use sub get_percentiles(\@ @) { my ($aref,@plist) = @_; my ($n, $last, $r, $d, $i, @vals, $Yp); $last = $#$aref; $n = $last + 1; #printf "%6d" x $n . "\n", @{$aref}; #printf "n: %4d, last: %d\n", $n, $last; foreach my $p (@plist) { $r = 1 + ($p * ($n - 1) / 100.0); $i = int ($r); # integer part # domain: $i = 1 .. n if ($i == $n) { $Yp = $aref->[$last]; } elsif ($i == 0) { $Yp = $aref->[0]; print "CAN'T HAPPEN: $Yp\n"; } else { $d = $r - $i; # decimal part #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1 $Yp = $aref->[$i-1] + ($d * ($aref->[$i] - $aref->[$i-1])); } #printf "\np(%6.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d", $p, $r, $i, $d, $Yp; push @vals, $Yp; } return @vals; } sub get_num_scores($) { my $scoretab_r = shift; my $totalscores = 0; for (my $i = 0; $i < @$scoretab_r; $i += 2) { $totalscores += $scoretab_r->[$i+1] } return $totalscores; } # scoretab # # (score1, n1), (score2, n2), ... (scoreN, nN) # $i $i+1 # # scores are 0 based (0 = 1st score) sub get_nth_score($ $) { my ($scoretab_r, $n) = @_; my $i = 0; my $n_cur_scores = 0; #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"; while ($i < $#$scoretab_r) { #print "Samples_seen: $n_cur_scores\n"; $n_cur_scores += $scoretab_r->[$i+1]; if ($n_cur_scores >= $n) { #printf "range: %s %s %s\n", $i >= 2 ? $scoretab_r->[$i - 2] : '', $scoretab_r->[$i], $i+2 > $#$scoretab_r ? '' : $scoretab_r->[$i + 2]; #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]; return $scoretab_r->[$i]; } $i += 2; } print "returning last score $scoretab_r->[$i]\n"; return $scoretab_r->[$i]; } sub get_percentiles2(\@ @) { my ($scoretab_r, @plist) = @_; my ($n, $last, $r, $d, $i, @vals, $Yp); #$last = $#$scoretab_r - 1; $n = get_num_scores($scoretab_r); #printf "\n%6d" x $n . "\n", @{$scoretab_r}; #printf "\n\tn: %4d, @$scoretab_r\n", $n; foreach my $p (@plist) { ###print "\nPERCENTILE: $p\n"; $r = 1 + ($p * ($n - 1) / 100.0); $i = int ($r); # integer part if ($i == $n) { #print "last:\n"; #$Yp = $scoretab_r->[$last]; $Yp = get_nth_score($scoretab_r, $n); } elsif ($i == 0) { #$Yp = $scoretab_r->[0]; print "1st: CAN'T HAPPEN\n"; $Yp = get_nth_score($scoretab_r, 1); } else { $d = $r - $i; # decimal part #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1 my $ithvalprev = get_nth_score($scoretab_r, $i); my $ithval = get_nth_score($scoretab_r, $i+1); $Yp = $ithvalprev + ($d * ($ithval - $ithvalprev)); } #printf "p(%6.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d\n", $p, $r, $i, $d, $Yp; push @vals, $Yp; } return @vals; } # Returns a list of frequency distributions given an incrementally sorted # set of sorted scores, and an incrementally sorted list of buckets # # Arg1 is an array ref to the sorted series # Arg2 is a list of frequency buckets to use sub get_frequencies(\@ @) { my ($aref,@blist) = @_; my @vals = ( 0 ) x (@blist); my @sorted_blist = sort { $a <=> $b } @blist; my $bucket_index = 0; OUTER: foreach my $score (@$aref) { #print "Score: $score\n"; for my $i ($bucket_index .. @sorted_blist - 1) { #print "\tTrying Bucket[$i]: $sorted_blist[$i]\n"; if ($score > $sorted_blist[$i]) { $bucket_index++; } else { #printf "\t\tinto Bucket[%d]\n", $bucket_index; $vals[$bucket_index]++; next OUTER; } } #printf "\t\tinto Bucket[%d]\n", $bucket_index - 1; $vals[$bucket_index - 1]++; } return @vals; } # Inserts commas in numbers for easier readability # sub commify ($) { return undef if ! defined ($_[0]); my $text = reverse $_[0]; $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; return scalar reverse $text; } # Unitize a number, and return appropriate printf formatting string # sub unitize($ $) { my ($num, $fmt) = @_; my $kilobyte = 2**10; my $megabyte = 2**20; my $gigabyte = 2**30; my $terabyte = 2**40; if ($num >= $terabyte) { $num /= $terabyte; $fmt .= '.3fT'; } elsif ($num >= $gigabyte) { $num /= $gigabyte; $fmt .= '.3fG'; } elsif ($num >= $megabyte) { $num /= $megabyte; $fmt .= '.3fM'; } elsif ($num >= $kilobyte) { $num /= $kilobyte; $fmt .= '.3fK'; } else { $fmt .= 'd '; } return ($num, $fmt); } # Returns a sublist of the supplied list of elements in an unchanged order, # where only the first occurrence of each defined element is retained # and duplicates removed # # Borrowed from amavis 2.6.2 # sub unique_list(@) { my ($r) = @_ == 1 && ref($_[0]) ? $_[0] : \@_; # accept list, or a list ref my (%seen); my (@unique) = grep { defined($_) && !$seen{$_}++ } @$r; return @unique; } # Generate a test maillog file from the '#TD' test data lines # The test data file is placed in /var/tmp/maillog.autogen # # arg1: "postfix" or "amavis" # arg2: path to postfix-logwatch or amavis-logwatch from which to read '#TD' data # # Postfix TD syntax: # TD() log entry # sub gen_test_log($) { my $scriptpath = shift; my $toolname = $Logreporters::progname_prefix; my $datafile = "/var/tmp/maillog-${toolname}.autogen"; die "gen_test_log: invalid toolname $toolname" if ($toolname !~ /^(postfix|amavis)$/); eval { require Sys::Hostname; require Fcntl; } or die "Unable to create test data file: required module(s) not found\n$@"; my $syslogtime = localtime; $syslogtime =~ s/^....(.*) \d{4}$/$1/; my ($hostname) = split /\./, Sys::Hostname::hostname(); # # avoid -T issues # delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; my $flags = &Fcntl::O_CREAT|&Fcntl::O_WRONLY|&Fcntl::O_TRUNC; sysopen(FH, $datafile, $flags) or die "Can't create test data file: $!"; print "Generating test log data file from $scriptpath: $datafile\n"; my $id; @ARGV = ($scriptpath); if ($toolname eq 'postfix') { my %services = ( DEF => 'smtpd', bQ => 'bounce', cN => 'cleanup', cQ => 'cleanup', lQ => 'local', m => 'master', p => 'pickup', pQ => 'pickup', ppQ => 'pipe', pfw => 'postfwd', pg => 'postgrey', pgQ => 'postgrey', ps => 'postsuper', qQ => 'qmgr', s => 'smtp', sQ => 'smtp', sd => 'smtpd', sdN => 'smtpd', sdQ => 'smtpd', spf => 'policy-spf', vN => 'virtual', vQ => 'virtual', ); $id = 'postfix/smtp[12345]'; while (<>) { if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) { my ($service,$count,$qid,$line) = ($1, $2, $3, $4); #print "SERVICE: %s, QID: %s, COUNT: %s, line: %s\n", $service, $qid, $count, $line; if ($service eq '') { $service = 'DEF'; } die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service}); $id = $services{$service} . '[123]'; $id = 'postfix/' . $id unless $services{$service} eq 'postgrey'; #print "searching for service: \"$service\"\n\tFound $id\n"; if ($service =~ /N$/) { $id .= ': NOQUEUE'; } elsif ($service =~ /Q$/) { $id .= $qid ? $qid : ': DEADBEEF'; } $line =~ s/ +/ /g; $line =~ s/^ //g; #print "$syslogtime $hostname $id: \"$line\"\n" x ($count ? $count : 1); print FH "$syslogtime $hostname $id: $line\n" x ($count ? $count : 1); } } } else { #amavis my %services = ( DEF => 'amavis', dcc => 'dccproc', ); while (<>) { if (/^\s*#TD([a-z]*)(\d+)? (.*)$/) { my ($service,$count,$line) = ($1, $2, $3); if ($service eq '') { $service = 'DEF'; } die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service}); $id = $services{$service} . '[123]:'; if ($services{$service} eq 'amavis') { $id .= ' (9999-99)'; } print FH "$syslogtime $hostname $id $line\n" x ($count ? $count : 1) } } } close FH or die "Can't close $datafile: $!"; } 1; #MODULE: ../Logreporters/Config.pm package Logreporters::Config; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.002'; @ISA = qw(Exporter); @EXPORT = qw(&init_run_mode &add_option &get_options &init_cmdline &get_vars_from_file &process_limiters &process_debug_opts &init_getopts_table_common &zero_opts @Optspec %Opts %Configvars @Limiters %line_styles $fw1 $fw2 $sep1 $sep2 &D_CONFIG &D_ARGS &D_VARS &D_TREE &D_SECT &D_UNMATCHED &D_TEST &D_ALL ); } use subs @EXPORT; our @Optspec = (); # options table used by Getopts our %Opts = (); # program-wide options our %Configvars = (); # configuration file variables our @Limiters; # Report separator characters and widths our ($fw1,$fw2) = (22, 10); our ($sep1,$sep2) = ('=', '-'); use Getopt::Long; BEGIN { import Logreporters::Utils qw(&get_usable_sectvars); } our %line_styles = ( truncate => 0, wrap => 1, full => 2, ); sub init_run_mode($); sub confighash_to_cmdline(\%); sub get_vars_from_file(\% $); sub process_limiters(\@); sub add_option(@); sub get_options($); sub init_getopts_table_common(@); sub set_supplemental_reports($$); # debug constants sub D_CONFIG () { 1<<0 } sub D_ARGS () { 1<<1 } sub D_VARS () { 1<<2 } sub D_TREE () { 1<<3 } sub D_SECT () { 1<<4 } sub D_UNMATCHED () { 1<<5 } sub D_TEST () { 1<<30 } sub D_ALL () { 1<<31 } my %debug_words = ( config => D_CONFIG, args => D_ARGS, vars => D_VARS, tree => D_TREE, sect => D_SECT, unmatched => D_UNMATCHED, test => D_TEST, all => 0xffffffff, ); # Clears %Opts hash and initializes basic running mode options in # %Opts hash by setting keys: 'standalone', 'detail', and 'debug'. # Call early. # sub init_run_mode($) { my $config_file = shift; $Opts{'debug'} = 0; # Logwatch passes a filter's options via environment variables. # When running standalone (w/out logwatch), use command line options $Opts{'standalone'} = exists ($ENV{LOGWATCH_DETAIL_LEVEL}) ? 0 : 1; # Show summary section by default $Opts{'summary'} = 1; if ($Opts{'standalone'}) { process_debug_opts($ENV{'LOGREPORTERS_DEBUG'}) if exists ($ENV{'LOGREPORTERS_DEBUG'}); } else { $Opts{'detail'} = $ENV{'LOGWATCH_DETAIL_LEVEL'}; # XXX #process_debug_opts($ENV{'LOGWATCH_DEBUG'}) if exists ($ENV{'LOGWATCH_DEBUG'}); } # first process --debug, --help, and --version options add_option ('debug=s', sub { process_debug_opts($_[1]); 1}); add_option ('version', sub { &Logreporters::Utils::get_version(); 1;}); get_options(1); # now process --config_file, so that all config file vars are read first add_option ('config_file|f=s', sub { get_vars_from_file(%Configvars, $_[1]); 1;}); get_options(1); # if no config file vars were read if ($Opts{'standalone'} and ! keys(%Configvars) and -f $config_file) { print "Using default config file: $config_file\n" if $Opts{'debug'} & D_CONFIG; get_vars_from_file(%Configvars, $config_file); } } sub get_options($) { my $pass_through = shift; #$SIG{__WARN__} = sub { print "*** $_[0]*** options error\n" }; # ensure we're called after %Opts is initialized die "get_options: program error: %Opts is empty" unless exists $Opts{'debug'}; my $p = new Getopt::Long::Parser; if ($pass_through) { $p->configure(qw(pass_through permute)); } else { $p->configure(qw(no_pass_through no_permute)); } #$p->configure(qw(debug)); if ($Opts{'debug'} & D_ARGS) { print "\nget_options($pass_through): enter\n"; printf "\tARGV(%d): ", scalar @ARGV; print @ARGV, "\n"; print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts; } if ($p->getoptions(\%Opts, @Optspec) == 0) { print STDERR "Use ${Logreporters::progname} --help for options\n"; exit 1; } if ($Opts{'debug'} & D_ARGS) { print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts; printf "\tARGV(%d): ", scalar @ARGV; print @ARGV, "\n"; print "get_options: exit\n"; } } sub add_option(@) { push @Optspec, @_; } # untaint string, borrowed from amavisd-new sub untaint($) { no re 'taint'; my ($str); if (defined($_[0])) { local($1); # avoid Perl taint bug: tainted global $1 propagates taintedness $str = $1 if $_[0] =~ /^(.*)$/; } return $str; } sub init_getopts_table_common(@) { my @supplemental_reports = @_; print "init_getopts_table_common: enter\n" if $Opts{'debug'} & D_ARGS; add_option ('help', sub { print STDOUT Logreporters::usage(undef); exit 0 }); add_option ('gen_test_log=s', sub { Logreporters::Utils::gen_test_log($_[1]); exit 0; }); add_option ('detail=i'); add_option ('nodetail', sub { # __none__ will set all limiters to 0 in process_limiters # since they are not known (Sections table is not yet built). push @Limiters, '__none__'; # 0 = disable supplemental_reports set_supplemental_reports(0, \@supplemental_reports); }); add_option ('max_report_width=i'); add_option ('summary!'); add_option ('show_summary=i', sub { $Opts{'summary'} = $_[1]; 1; }); # untaint ipaddr_width for use w/sprintf() in Perl v5.10 add_option ('ipaddr_width=i', sub { $Opts{'ipaddr_width'} = untaint ($_[1]); 1; }); add_option ('sect_vars!'); add_option ('show_sect_vars=i', sub { $Opts{'sect_vars'} = $_[1]; 1; }); add_option ('syslog_name=s'); add_option ('wrap', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; }); add_option ('full', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; }); add_option ('truncate', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; }); add_option ('line_style=s', sub { my $style = lc($_[1]); my @list = grep (/^$style/, keys %line_styles); if (! @list) { print STDERR "Invalid line_style argument \"$_[1]\"\n"; print STDERR "Option line_style argument must be one of \"wrap\", \"full\", or \"truncate\".\n"; print STDERR "Use $Logreporters::progname --help for options\n"; exit 1; } $Opts{'line_style'} = $line_styles{lc($list[0])}; 1; }); add_option ('limit|l=s', sub { my ($limiter,$lspec) = split(/=/, $_[1]); if (!defined $lspec) { printf STDERR "Limiter \"%s\" requires value (ex. --limit %s=10)\n", $_[1],$_[1]; exit 2; } foreach my $val (split(/(?:\s+|\s*,\s*)/, $lspec)) { if ($val !~ /^\d+$/ and $val !~ /^(\d*)\.(\d+)$/ and $val !~ /^::(\d+)$/ and $val !~ /^:(\d+):(\d+)?$/ and $val !~ /^(\d+):(\d+)?:(\d+)?$/) { printf STDERR "Limiter value \"$val\" invalid in \"$limiter=$lspec\"\n"; exit 2; } } push @Limiters, lc $_[1]; }); print "init_getopts_table_common: exit\n" if $Opts{'debug'} & D_ARGS; } sub get_option_names() { my (@ret, @tmp); foreach (@Optspec) { if (ref($_) eq '') { # process only the option names my $spec = $_; $spec =~ s/=.*$//; $spec =~ s/([^|]+)\!$/$1|no$1/g; @tmp = split /[|]/, $spec; #print "PUSHING: @tmp\n"; push @ret, @tmp; } } return @ret; } # Set values for the configuration variables passed via hashref. # Variables are of the form ${progname_prefix}_KEYNAME. # # Because logwatch lowercases all config file entries, KEYNAME is # case-insensitive. # sub init_cmdline() { my ($href, $configvar, $value, $var); # logwatch passes all config vars via environment variables $href = $Opts{'standalone'} ? \%Configvars : \%ENV; # XXX: this is cheeze: need a list of valid limiters, but since # the Sections table is not built yet, we don't know what is # a limiter and what is an option, as there is no distinction in # variable names in the config file (perhaps this should be changed). my @valid_option_names = get_option_names(); die "Options table not yet set" if ! scalar @valid_option_names; print "confighash_to_cmdline: @valid_option_names\n" if $Opts{'debug'} & D_ARGS; my @cmdline = (); while (($configvar, $value) = each %$href) { if ($configvar =~ s/^${Logreporters::progname_prefix}_//o) { # distinguish level limiters from general options # would be easier if limiters had a unique prefix $configvar = lc $configvar; my $ret = grep (/^$configvar$/i, @valid_option_names); if ($ret == 0) { print "\tLIMITER($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS; push @cmdline, '-l', "$configvar" . "=$value"; } else { print "\tOPTION($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS; unshift @cmdline, $value if defined ($value); unshift @cmdline, "--$configvar"; } } } unshift @ARGV, @cmdline; } # Obtains the variables from a logwatch-style .conf file, for use # in standalone mode. Returns an ENV-style hash of key/value pairs. # sub get_vars_from_file(\% $) { my ($href, $file) = @_; my ($var, $val); print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & D_CONFIG; my $message = undef; my $ret = stat ($file); if ($ret == 0) { $message = $!; } elsif (! -r _) { $message = "Permission denied"; } elsif ( -d _) { $message = "Is a directory"; } elsif (! -f _) { $message = "Not a regular file"; } if ($message) { print STDERR "Configuration file \"$file\": $message\n"; exit 2; } my $prog = $Logreporters::progname_prefix; open FILE, '<', "$file" or die "unable to open configuration file $file: $!"; while () { chomp; next if (/^\s*$/); # ignore all whitespace lines next if (/^\*/); # ignore logwatch's *Service lines next if (/^\s*#/); # ignore comment lines if (/^\s*\$(${prog}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) { ($var,$val) = ($1,$2); if ($val =~ /^(?:no|false)$/i) { $val = 0; } elsif ($val =~ /^(?:yes|true)$/i) { $val = 1; } elsif ($val eq '') { $var =~ s/${prog}_/${prog}_no/; $val = undef; } print "\t\"$var\" => \"$val\"\n" if $Opts{'debug'} & D_CONFIG; $href->{$var} = $val; } } close FILE or die "failed to close configuration handle for $file: $!"; print "get_vars_from_file: exit\n" if $Opts{'debug'} & D_CONFIG; } sub process_limiters(\@) { my ($sectref) = @_; my ($limiter, $var, $val, @errors); my @l = get_usable_sectvars(@$sectref, 1); if ($Opts{'debug'} & D_VARS) { print "process_limiters: enter\n"; print "\tLIMITERS: @Limiters\n"; } while ($limiter = shift @Limiters) { my @matched = (); printf "\t%-30s ",$limiter if $Opts{'debug'} & D_VARS; # disable all limiters when limiter is __none__: see 'nodetail' cmdline option if ($limiter eq '__none__') { $Opts{$_} = 0 foreach @l; next; } ($var,$val) = split /=/, $limiter; if ($val eq '') { push @errors, "Limiter \"$var\" requires value (ex. --limit limiter=10)"; next; } # try exact match first, then abbreviated match next if (scalar (@matched = grep(/^$var$/, @l)) == 1 or scalar (@matched = grep(/^$var/, @l)) == 1) { $limiter = $matched[0]; # unabbreviate limiter print "MATCH: $var: $limiter => $val\n" if $Opts{'debug'} & D_VARS; # XXX move limiters into section hash entry... $Opts{$limiter} = $val; next; } print "matched=", scalar @matched, ": @matched\n" if $Opts{'debug'} & D_VARS; push @errors, "Limiter \"$var\" is " . (scalar @matched == 0 ? "invalid" : "ambiguous: @matched"); } print "\n" if $Opts{'debug'} & D_VARS; if (@errors) { print STDERR "$_\n" foreach @errors; exit 2; } # Set the default value of 10 for each section if no limiter exists. # This allows output for each section should there be no configuration # file or missing limiter within the configuration file. foreach (@l) { $Opts{$_} = 10 unless exists $Opts{$_}; } # Enable collection for each section if a limiter is non-zero. foreach (@l) { #print "L is: $_\n"; #print "DETAIL: $Opts{'detail'}, OPTS: $Opts{$_}\n"; $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0; } #print "OPTS: \n"; map { print "$_ => $Opts{$_}\n"} keys %Opts; #print "COLLECTING: \n"; map { print "$_ => $Logreporters::TreeData::Collecting{$_}\n"} keys %Logreporters::TreeData::Collecting; } # Enable/disable supplemental reports # arg1: 0=off, 1=on # arg2,...: list of supplemental report keywords sub set_supplemental_reports($$) { my ($onoff,$aref) = @_; $Opts{$_} = $onoff foreach (@$aref); } sub process_debug_opts($) { my $optstring = shift; my @errors = (); foreach (split(/\s*,\s*/, $optstring)) { my $word = lc $_; my @matched = grep (/^$word/, keys %debug_words); if (scalar @matched == 1) { $Opts{'debug'} |= $debug_words{$matched[0]}; next; } if (scalar @matched == 0) { push @errors, "Unknown debug keyword \"$word\""; } else { # > 1 push @errors, "Ambiguous debug keyword abbreviation \"$word\": (matches: @matched)"; } } if (@errors) { print STDERR "$_\n" foreach @errors; print STDERR "Debug keywords: ", join (' ', sort keys %debug_words), "\n"; exit 2; } } # Zero the options controlling level specs and those # any others passed via Opts key. # # Zero the options controlling level specs in the # Detailed section, and set all other report options # to disabled. This makes it easy via command line to # disable the entire summary section, and then re-enable # one or more sections for specific reports. # # eg. progname --nodetail --limit forwarded=2 # sub zero_opts ($ @) { my $sectref = shift; # remaining args: list of Opts keys to zero map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & D_VARS;} @_; map { $Opts{$_} = 0 } get_usable_sectvars(@$sectref, 1); } 1; #MODULE: ../Logreporters/TreeData.pm package Logreporters::TreeData; use 5.008; use strict; use re 'taint'; use warnings; no warnings "uninitialized"; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.001'; @ISA = qw(Exporter); @EXPORT = qw(%Totals %Counts %Collecting $END_KEY); @EXPORT_OK = qw(&printTree &buildTree); } use subs @EXPORT_OK; BEGIN { import Logreporters::Config qw(%line_styles); } # Totals and Counts are the log line accumulator hashes. # Totals: maintains per-section grand total tallies for use in Summary section # Counts: is a multi-level hash, which maintains per-level key totals. our (%Totals, %Counts); # The Collecting hash determines which sections will be captured in # the Counts hash. Counts are collected only if a section is enabled, # and this hash obviates the need to test both existence and # non-zero-ness of the Opts{'keyname'} (either of which cause capture). # XXX The Opts hash could be used .... our %Collecting = (); sub buildTree(\% $ $ $ $ $); sub printTree($ $ $ $ $); =pod [ a:b:c, ... ] which would be interpreted as follows: a = show level a detail b = show at most b items at this level c = minimum count that will be shown =cut sub printTree($ $ $ $ $) { my ($treeref, $lspecsref, $line_style, $max_report_width, $debug) = @_; my ($entry, $line); my $cutlength = $max_report_width - 3; my $topn = 0; foreach $entry (sort bycount @$treeref) { ref($entry) ne "HASH" and die "Unexpected entry in tree: $entry\n"; #print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n"; # Once the top N lines have been printed, we're done if ($lspecsref->[$entry->{LEVEL}]{topn}) { if ($topn++ >= $lspecsref->[$entry->{LEVEL}]{topn} ) { print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n" unless ($debug) and do { $line = ' ' . ' ' x ($entry->{LEVEL} + 3) . '...'; printf "%-130s L%d: topn reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{topn}; }; last; } } # Once the item's count falls below the given threshold, we're done at this level # unless a top N is specified, as threshold has lower priority than top N elsif ($lspecsref->[$entry->{LEVEL}]{threshold}) { if ($entry->{TOTAL} <= $lspecsref->[$entry->{LEVEL}]{threshold}) { print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n" unless ($debug) and do { $line = ' ' . (' ' x ($entry->{LEVEL} + 3)) . '...'; printf "%-130s L%d: threshold reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{threshold}; }; last; } } $line = sprintf "%8d%s%s", $entry->{TOTAL}, ' ' x ($entry->{LEVEL} + 2), $entry->{DATA}; if ($debug) { printf "%-130s %-60s\n", $line, $entry->{DEBUG}; } # line_style full, or lines < max_report_width #printf "MAX: $max_report_width, LEN: %d, CUTLEN $cutlength\n", length($line); if ($line_style == $line_styles{'full'} or length($line) <= $max_report_width) { print $line, "\n"; } elsif ($line_style == $line_styles{'truncate'}) { print substr ($line,0,$cutlength), '...', "\n"; } elsif ($line_style == $line_styles{'wrap'}) { my $leader = ' ' x 8 . ' ' x ($entry->{LEVEL} + 2); print substr ($line, 0, $max_report_width, ''), "\n"; while (length($line)) { print $leader, substr ($line, 0, $max_report_width - length($leader), ''), "\n"; } } else { die ('unexpected line style'); } printTree ($entry->{CHILDREF}, $lspecsref, $line_style, $max_report_width, $debug) if (exists $entry->{CHILDREF}); } } 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/; # XXX optimize this using packed default sorting. Analysis shows speed isn't an issue though sub bycount { # Sort by totals, then IP address if one exists, and finally by data as a string local $SIG{__WARN__} = sub { print "*** PLEASE REPORT:\n*** $_[0]*** Unexpected: \"$a->{DATA}\", \"$b->{DATA}\"\n" }; $b->{TOTAL} <=> $a->{TOTAL} || pack('C4' => $a->{DATA} =~ /^$re_IP_strict/o) cmp pack('C4' => $b->{DATA} =~ /^$re_IP_strict/o) || $a->{DATA} cmp $b->{DATA} } # # Builds a tree of REC structures from the multi-key %Counts hashes # # Parameters: # Hash: A multi-key hash, with keys being used as category headings, and leaf data # being tallies for that set of keys # Level: This current recursion level. Call with 0. # # Returns: # Listref: A listref, where each item in the list is a rec record, described as: # DATA: a string: a heading, or log data # TOTAL: an integer: which is the subtotal of this item's children # LEVEL: an integer > 0: representing this entry's level in the tree # CHILDREF: a listref: references a list consisting of this node's children # Total: The cumulative total of items found for a given invocation # # Use the special key variable $END_KEY, which is "\a\a" (two ASCII bell's) to end a, # nested hash early, or the empty string '' may be used as the last key. our $END_KEY = "\a\a"; sub buildTree(\% $ $ $ $ $) { my ($href, $max_level_section, $levspecref, $max_level_global, $recurs_level, $show_unique, $debug) = @_; my ($subtotal, $childList, $rec); my @treeList = (); my $total = 0; foreach my $item (sort keys %$href) { if (ref($href->{$item}) eq "HASH") { #print " " x ($recurs_level * 4), "HASH: LEVEL $recurs_level: Item: $item, type: \"", ref($href->{$item}), "\"\n"; ($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_section, $levspecref, $max_level_global, $recurs_level + 1, $debug); if ($recurs_level < $max_level_global and $recurs_level < $max_level_section) { # me + children $rec = { DATA => $item, TOTAL => $subtotal, LEVEL => $recurs_level, CHILDREF => $childList, }; if ($debug) { $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d", $recurs_level + 1, $max_level_global, $max_level_section, $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $subtotal; } push (@treeList, $rec); } } else { if ($item ne '' and $item ne $END_KEY and $recurs_level < $max_level_global and $recurs_level < $max_level_section) { $rec = { DATA => $item, TOTAL => $href->{$item}, LEVEL => $recurs_level, #CHILDREF => undef, }; if ($debug) { $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d", $recurs_level, $max_level_global, $max_level_section, $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $href->{$item}; } push (@treeList, $rec); } $subtotal = $href->{$item}; } $total += $subtotal; } #print " " x ($recurs_level * 4), "LEVEL $recurs_level: Returning from recurs_level $recurs_level\n"; return ($total, \@treeList); } 1; #MODULE: ../Logreporters/Reports.pm package Logreporters::Reports; use 5.008; use strict; use re 'taint'; use warnings; no warnings "uninitialized"; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.002'; @ISA = qw(Exporter); @EXPORT = qw(&inc_unmatched &print_unmatched_report &print_percentiles_report2 &print_summary_report &print_detail_report); @EXPORT_OK = qw(); } use subs @EXPORT_OK; BEGIN { import Logreporters::Config qw(%Opts $fw1 $fw2 $sep1 $sep2 &D_UNMATCHED &D_TREE); import Logreporters::Utils qw(&commify &unitize &get_percentiles &get_percentiles2); import Logreporters::TreeData qw(%Totals %Counts &buildTree &printTree); } my (%unmatched_list); our $origline; # unmodified log line, for error reporting and debug sub inc_unmatched($) { my ($id) = @_; $unmatched_list{$origline}++; print "UNMATCHED($id): \"$origline\"\n" if $Opts{'debug'} & D_UNMATCHED; } # Print unmatched lines # sub print_unmatched_report() { return unless (keys %unmatched_list); print "\n\n**Unmatched Entries**\n"; foreach my $line (sort {$unmatched_list{$b}<=>$unmatched_list{$a} } keys %unmatched_list) { printf "%8d %s\n", $unmatched_list{$line}, $line; } } =pod ****** Summary ******************************************************** 2 Miscellaneous warnings 20621 Total messages scanned ---------------- 100.00% 662.993M Total bytes scanned 695,198,092 ======== ================================================ 19664 Ham ----------------------------------- 95.36% 19630 Clean passed 95.19% 34 Bad header passed 0.16% 942 Spam ---------------------------------- 4.57% 514 Spam blocked 2.49% 428 Spam discarded (no quarantine) 2.08% 15 Malware ------------------------------- 0.07% 15 Malware blocked 0.07% 1978 SpamAssassin bypassed 18 Released from quarantine 1982 Whitelisted 3 Blacklisted 12 MIME error 51 Bad header (debug supplemental) 28 Extra code modules loaded at runtime =cut # Prints the Summary report section # sub print_summary_report (\@) { my ($sections) = @_; my ($keyname,$cur_level); my @lines; my $expand_header_footer = sub { my $line = undef; foreach my $horf (@_) { # print blank line if keyname is newline if ($horf eq "\n") { $line .= "\n"; } elsif (my ($sepchar) = ($horf =~ /^(.)$/o)) { $line .= sprintf "%s %s\n", $sepchar x 8, $sepchar x 50; } else { die "print_summary_report: unsupported header or footer type \"$horf\""; } } return $line; }; if ($Opts{'detail'} >= 5) { my $header = "****** Summary "; print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n\n"; } my @headers; foreach my $sref (@$sections) { # headers and separators die "Unexpected Section $sref" if (ref($sref) ne 'HASH'); # Start of a new section group. # Expand and save headers to output at end of section group. if ($sref->{CLASS} eq 'GROUP_BEGIN') { $cur_level = $sref->{LEVEL}; $headers[$cur_level] = &$expand_header_footer(@{$sref->{HEADERS}}); } elsif ($sref->{CLASS} eq 'GROUP_END') { my $prev_level = $sref->{LEVEL}; # If this section had lines to output, tack on headers and footers, # removing extraneous newlines. if ($lines[$cur_level]) { # squish multiple blank lines if ($headers[$cur_level] and substr($headers[$cur_level],0,1) eq "\n") { if ( ! defined $lines[$prev_level][-1] or $lines[$prev_level][-1] eq "\n") { $headers[$cur_level] =~ s/^\n+//; } } push @{$lines[$prev_level]}, $headers[$cur_level] if $headers[$cur_level]; push @{$lines[$prev_level]}, @{$lines[$cur_level]}; my $f = &$expand_header_footer(@{$sref->{FOOTERS}}); push @{$lines[$prev_level]}, $f if $f; $lines[$cur_level] = undef; } $headers[$cur_level] = undef; $cur_level = $prev_level; } elsif ($sref->{CLASS} eq 'DATA') { # Totals data $keyname = $sref->{NAME}; if ($Totals{$keyname} > 0) { my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR}); my $fmt = '%8'; my $extra = ' %25s'; my $total = $Totals{$keyname}; # Z format provides unitized or unaltered totals, as appropriate if ($numfmt eq 'Z') { ($total, $fmt) = unitize ($total, $fmt); } else { $fmt .= "$numfmt "; $extra = ''; } if ($divisor and $$divisor) { # XXX generalize this if (ref ($desc) eq 'ARRAY') { $desc = @$desc[0] . ' ' . @$desc[1] x (42 - 2 - length(@$desc[0])); } push @{$lines[$cur_level]}, sprintf "$fmt %-42s %6.2f%%\n", $total, $desc, $$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor; } else { my $out = sprintf "$fmt %-23s", $total, $desc; if ($extra) { $out .= sprintf " $extra", commify ($Totals{$keyname}); } $out .= "\n"; push @{$lines[$cur_level]}, $out; } } } else { die "print_summary_report: unexpected control..."; } } print @{$lines[0]}; print "\n"; } # Prints the Detail report section # # Note: side affect; deletes each key in Totals/Counts # after printout. Only the first instance of a key in # the Section table will result in Detail output. sub print_detail_report (\@) { my ($sections) = @_; my $header_printed = 0; return unless (keys %Counts); #use Devel::Size qw(size total_size); foreach my $sref ( @$sections ) { next unless $sref->{CLASS} eq 'DATA'; # only print detail for this section if DETAIL is enabled # and there is something in $Counts{$keyname} next unless $sref->{DETAIL}; next unless exists $Counts{$sref->{NAME}}; my $keyname = $sref->{NAME}; my $max_level = undef; my $print_this_key = 0; my @levelspecs = (); clear_level_specs($max_level, \@levelspecs); if (exists $Opts{$keyname}) { $max_level = create_level_specs($Opts{$keyname}, $Opts{'detail'}, \@levelspecs); $print_this_key = 1 if ($max_level); } else { $print_this_key = 1; } #print_level_specs($max_level,\@levelspecs); # at detail 5, print level 1, detail 6: level 2, ... #print STDERR "building: $keyname\n"; my ($count, $treeref) = buildTree (%{$Counts{$keyname}}, defined ($max_level) ? $max_level : 11, \@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & D_TREE); if ($count > 0) { if ($print_this_key) { my $desc = $sref->{TITLE}; $desc =~ s/^\s+//; if (! $header_printed) { my $header = "****** Detail ($max_level) "; print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n"; $header_printed = 1; } printf "\n%8d %s %s\n", $count, $desc, $Opts{'sect_vars'} ? ('-' x ($Opts{'max_report_width'} - 18 - length($desc) - length($keyname))) . " [ $keyname ] -" : '-' x ($Opts{'max_report_width'} - 12 - length($desc)) } printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'}, $Opts{'debug'} & D_TREE); } #print STDERR "Total size Counts: ", total_size(\%Counts), "\n"; #print STDERR "Total size Totals: ", total_size(\%Totals), "\n"; $treeref = undef; $Totals{$keyname} = undef; delete $Totals{$keyname}; delete $Counts{$keyname}; } #print "\n"; } =pod Print out a standard percentiles report === Delivery Delays Percentiles =============================================================== 0% 25% 50% 75% 90% 95% 98% 100% ----------------------------------------------------------------------------------------------- Before qmgr 0.01 0.70 1.40 45483.70 72773.08 81869.54 87327.42 90966.00 In qmgr 0.00 0.00 0.00 0.01 0.01 0.01 0.01 0.01 Conn setup 0.00 0.00 0.00 0.85 1.36 1.53 1.63 1.70 Transmission 0.03 0.47 0.92 1.61 2.02 2.16 2.24 2.30 Total 0.05 1.18 2.30 45486.15 72776.46 81873.23 87331.29 90970.00 =============================================================================================== === Postgrey Delays Percentiles =========================================================== 0% 25% 50% 75% 90% 95% 98% 100% ------------------------------------------------------------------------------------------- Postgrey 727.00 727.00 727.00 727.00 727.00 727.00 727.00 727.00 =========================================================================================== tableref: data table: ref to array of arrays, first cell is label, subsequent cells are data title: table's title percentiles_str: string of space or comma separated integers, which are the percentiles calculated and output as table column data =cut sub print_percentiles_report2($$$) { my ($tableref, $title, $percentiles_str) = @_; return unless @$tableref; my $myfw2 = $fw2 - 1; my @percents = split /[ ,]/, $percentiles_str; # Calc y label width from the hash's keys. Each key is padded with the # string "#: ", # where # is a single-digit sort index. my $y_label_max_width = 0; for (@$tableref) { $y_label_max_width = length($_->[0]) if (length($_->[0]) > $y_label_max_width); } # Titles row my $col_titles_str = sprintf "%-${y_label_max_width}s" . "%${myfw2}s%%" x @percents , ' ', @percents; my $table_width = length($col_titles_str); # Table header row my $table_header_str = sprintf "%s %s ", $sep1 x 3, $title; $table_header_str .= $sep1 x ($table_width - length($table_header_str)); print "\n", $table_header_str; print "\n", $col_titles_str; print "\n", $sep2 x $table_width; my (@p, @coldata, @xformed); foreach (@$tableref) { my ($title, $ref) = ($_->[0], $_->[1]); #xxx my @sorted = sort { $a <=> $b } @{$_->[1]}; my @byscore = (); for my $bucket (sort { $a <=> $b } keys %$ref) { #print "Key: $title: Bucket: $bucket = $ref->{$bucket}\n"; # pairs: bucket (i.e. key), tally push @byscore, $bucket, $ref->{$bucket}; } my @p = get_percentiles2 (@byscore, @percents); printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), $title, @p; } =pod foreach (@percents) { #printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), substr($title,3), @p; printf "\n%3d%%", $title; foreach my $val (@{shift @xformed}) { my $unit; if ($val > 1000) { $unit = 's'; $val /= 1000; } else { $unit = ''; } printf "%${fw3}.2f%-2s", $val, $unit; } } =cut print "\n", $sep1 x $table_width, "\n"; } sub clear_level_specs($ $) { my ($max_level,$lspecsref) = @_; #print "Zeroing $max_level rows of levelspecs\n"; $max_level = 0 if (not defined $max_level); for my $x (0..$max_level) { $lspecsref->[$x]{topn} = undef; $lspecsref->[$x]{threshold} = undef; } } # topn = 0 means don't limit # threshold = 0 means no min threshold sub create_level_specs($ $ $) { my ($optkey,$gdetail,$lspecref) = @_; return 0 if ($optkey eq "0"); my $max_level = $gdetail; # default to global detail level my (@specsP1, @specsP2, @specsP3); #printf "create_level_specs: key: %s => \"%s\", max_level: %d\n", $optkey, $max_level; foreach my $sp (split /[\s,]+/, $optkey) { #print "create_level_specs: SP: \"$sp\"\n"; # original level specifier if ($sp =~ /^\d+$/) { $max_level = $sp; #print "create_level_specs: max_level set: $max_level\n"; } # original level specifier + topn at level 1 elsif ($sp =~ /^(\d*)\.(\d+)$/) { if ($1) { $max_level = $1; } else { $max_level = $gdetail; } # top n specified, but no max level # force top N at level 1 (zero based) push @specsP1, { level => 0, topn => $2, threshold => 0 }; } # newer level specs elsif ($sp =~ /^::(\d+)$/) { push @specsP3, { level => undef, topn => 0, threshold => $1 }; } elsif ($sp =~ /^:(\d+):(\d+)?$/) { push @specsP2, { level => undef, topn => $1, threshold => defined $2 ? $2 : 0 }; } elsif ($sp =~ /^(\d+):(\d+)?:(\d+)?$/) { push @specsP1, { level => ($1 > 0 ? $1 - 1 : 0), topn => $2 ? $2 : 0, threshold => $3 ? $3 : 0 }; } else { print STDERR "create_level_specs: unexpected levelspec ignored: \"$sp\"\n"; } } #foreach my $sp (@specsP3, @specsP2, @specsP1) { # printf "Sorted specs: L%d, topn: %3d, threshold: %3d\n", $sp->{level}, $sp->{topn}, $sp->{threshold}; #} my ($min, $max); foreach my $sp ( @specsP3, @specsP2, @specsP1) { ($min, $max) = (0, $max_level); if (defined $sp->{level}) { $min = $max = $sp->{level}; } for my $level ($min..$max) { #printf "create_level_specs: setting L%d, topn: %s, threshold: %s\n", $level, $sp->{topn}, $sp->{threshold}; $lspecref->[$level]{topn} = $sp->{topn} if ($sp->{topn}); $lspecref->[$level]{threshold} = $sp->{threshold} if ($sp->{threshold}); } } return $max_level; } sub print_level_specs($ $) { my ($max_level,$lspecref) = @_; for my $level (0..$max_level) { printf "LevelSpec Row %d: %3d %3d\n", $level, $lspecref->[$level]{topn}, $lspecref->[$level]{threshold}; } } 1; package Logreporters; BEGIN { import Logreporters::Utils; import Logreporters::Config; import Logreporters::TreeData qw(%Totals %Counts %Collecting printTree buildTree); import Logreporters::Reports; } use 5.008; use strict; use warnings; no warnings "uninitialized"; use re 'taint'; use Getopt::Long; use File::Basename; our $progname = fileparse($0); # the list of supplemental reports available in the Detail section #p0f my @supplemental_reports = qw( autolearn score_percentiles score_frequencies sarules timings sa_timings startinfo ); # Default values for various options, used if no config file exists, # or some option is not set. # # These are used to reset default values after an option has been # disabled (via undef'ing its value). This allows a report to be # disabled via config file or --nodetail, but reenabled via subsequent # command line option my %Defaults = ( detail => 10, # report level detail max_report_width => 100, # maximum line width for report output line_style => undef, # lines > max_report_width, 0=truncate,1=wrap,2=full syslog_name => $progname_prefix, # amavis' syslog service name sect_vars => 0, # show section vars in detail report hdrs ipaddr_width => 15, # width for printing ip addresses first_recip_only => 0, # Show only the first recipient, or all autolearn => 1, # show Autolearn report bayes => 1, # show hit Bayesian buckets #p0f => 'all all', # p0f hits report sarules => '20 20', # show SpamAssassin rules hit score_frequencies => '-10 -5 0 5 10 20 30', # buckets shown in spam scores report score_percentiles => '0 50 90 95 98 100', # percentiles shown in spam scores report startinfo => 1, # show amavis startup info timings => 95, # show top N% of the timings report timings_percentiles => '0 5 25 50 75 95 100', # percentiles shown in timing report sa_timings => 95, # show top N% of the SA timings report sa_timings_percentiles => '0 5 25 50 75 95 100', # percentiles shown in SA timing report ); my $usage_str = <<"END_USAGE"; Usage: $progname [ ARGUMENTS ] [logfile ...] ARGUMENTS can be one or more of options listed below. Later options override earlier ones. Any argument may be abbreviated to an unambiguous length. Input comes from named logfiles, or STDIN. --debug AREAS provide debug output for AREAS --help print usage information --version print program version --config_file FILE, -f FILE use alternate configuration file FILE --syslog_name PATTERN only consider log lines that match syslog service name PATTERN --detail LEVEL print LEVEL levels of detail (default: 10) --nodetail set all detail levels to 0 --[no]summary display the summary section --ipaddr_width WIDTH use WIDTH chars for IP addresses in address/hostname pairs --line_style wrap|full|truncate disposition of lines > max_report_width (default: truncate) --full same as --line_style=full --truncate same as --line_style=truncate --wrap same as --line_style=wrap --max_report_width WIDTH limit report width to WIDTH chars (default: 100) --limit L=V, -l L=V set level limiter L with value V --[no]sect_vars [do not] show config file var/cmd line option names in section titles --[no]autolearn show autolearn report --[no]by_ccat_summary include by contents category grouping in summary --[no]first_recip_only show first recipient only, or all recipients --nosarules disable SpamAssassin spam and ham rules hit reports --sarules "S,H" enable SpamAssassin spam and ham rules reports, showing --sarules "default" showing the top S spam and top H ham rules hit (range: 0..., "all", or the keyword "default"). --noscore_frequencies disable spam score frequency report --score_frequencies "B1 [B2 ...]" enable spam score frequency report, using buckets --score_frequencies "default" specified with B1 [B2 ...] (range: real numbers), or using their internal default values when the keyword "default" is given --noscore_percentiles disable spam score percentiles report --score_percentiles "P1 [P2 ...]" enable spam score percentiles report, using percentiles --score_percentiles "default" specified with P1 [P2 ...] (range: 0...100), or using their internal default values when the keyword "default" is given --[no]startinfo show latest amavis startup details, if available --nosa_timings disable the SA timings report (same as --sa_timings 0) --sa_timings PERCENT show top PERCENT percent of the SA timings report (range: 0...100) --sa_timings_percentiles "P1 [P2 ...]" set SA timings report percentiles to P1 [P2 ...] (range: 0...100) --notimings disable the timings report (same as --timings 0) --timings PERCENT show top PERCENT percent of the timings report (range: 0...100) --timings_percentiles "P1 [P2 ...]" set timings report percentiles to P1 [P2 ...] (range: 0...100) END_USAGE # local prototypes sub usage($); sub init_getopts_table(); sub init_defaults(); sub build_sect_table(); sub parse_vals($$); sub triway_opts($$); sub printSpamScorePercentilesReport; sub printSpamScoreFrequencyReport; sub printAutolearnReport; sub printSARulesReport; sub printTimingsReport($$$$); sub printStartupInfoReport; sub strip_trace($); sub prioritize_cmdline(@); sub create_ignore_list(); sub check_ignore_list($ \@); # lines that match any RE in this list will be ignored. # see create_ignore_list(); my @ignore_list_final = (); # The Sections table drives Summary and Detail reports. For each entry in the # table, if there is data available, a line will be output in the Summary report. # Additionally, a sub-section will be output in the Detail report if both the # global --detail, and the section's limiter variable, are sufficiently high (a # non-existent section limiter variable is considered to be sufficiently high). # my @Sections; # Initialize main running mode and basic opts init_run_mode($config_file); # Configure the Getopts options table init_getopts_table(); # Place configuration file/environment variables onto command line init_cmdline(); # Initialize default values init_defaults(); # Process command line arguments, 0=no_permute,no_pass_through get_options(0); # Build the Section table build_sect_table(); # Run through the list of Limiters, setting the limiters in %Opts. process_limiters(@Sections); # Set collection for any enabled supplemental sections foreach (@supplemental_reports) { $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0; } # Don't collect SpamScores when not necessary $Collecting{'spamscores'} = ($Opts{'detail'} >= 5 && ($Opts{'score_percentiles'} || $Opts{'score_frequencies'})) ? 1 : 0; if (! defined $Opts{'line_style'}) { # default line style to full if detail >= 11, or truncate otherwise $Opts{'line_style'} = ($Opts{'detail'} > 10) ? $line_styles{'full'} : $line_styles{'truncate'}; } # Create the list of REs used to match against log lines create_ignore_list(); my (%Timings, %TimingsSA, @TimingsTotals, @TimingsSATotals); my (%SaveLine, %StartInfo); my (%SpamScores, %spamtags, %p0ftags); # Priority: VIRUS BANNED UNCHECKED SPAM SPAMMY BADH OVERSIZED MTA CLEAN my %ccatmajor_to_sectkey = ( 'INFECTED' => 'malware', 'BANNED' => 'bannedname', 'UNCHECKED' => 'unchecked', 'SPAM' => 'spam', 'SPAMMY' => 'spammy', 'BAD-HEADER' => 'badheader', 'OVERSIZED' => 'oversized', 'MTA-BLOCKED' => 'mta', 'CLEAN' => 'clean', 'TEMPFAIL' => 'tempfail', 'OTHER' => 'other', ); my %ccatmajor_to_priority = ( 'INFECTED' => 9, 'BANNED' => 8, 'UNCHECKED' => 7, 'SPAM' => 6, 'SPAMMY' => 5, 'BAD-HEADER' => 4, 'OVERSIZED' => 3, 'MTA-BLOCKED' => 2, 'CLEAN' => 1, 'TEMPFAIL' => 0, 'OTHER' => 0, ); # for reports my %ccatmajor_to_spamham = ( 'INFECTED' => 'malware', 'BANNED' => 'bannedname', 'UNCHECKED' => 'unchecked', 'SPAM' => 'spam', 'SPAMMY' => 'spam', 'BAD-HEADER' => 'ham', 'OVERSIZED' => 'ham', 'MTA-BLOCKED' => 'ham', 'CLEAN' => 'ham', 'TEMPFAIL' => 'ham', 'OTHER' => 'ham', ); my $logline_maxlen = 980; # Create the list of REs against which log lines are matched. # Lines that match any of the patterns in this list are ignored. # # Note: This table is created at runtime, due to a Perl bug which # I reported as perl bug #56202: # # http://rt.perl.org/rt3/Public/Bug/Display.html?id=56202 # sub create_ignore_list() { push @ignore_list_final, qr/^lookup_ip_acl/; push @ignore_list_final, qr/^lookup_acl/; push @ignore_list_final, qr/^lookup_hash/; push @ignore_list_final, qr/^lookup_re/; push @ignore_list_final, qr/^lookup_ldap/; push @ignore_list_final, qr/^lookup_sql_field.* result=[YN]$/; push @ignore_list_final, qr/^lookup .* does not match$/; push @ignore_list_final, qr/^lookup [[(]/; push @ignore_list_final, qr/^lookup => /; push @ignore_list_final, qr/^lookup: /; push @ignore_list_final, qr/^save_info_preliminary/; # log level 4 push @ignore_list_final, qr/^save_info_final/; # log level 4 push @ignore_list_final, qr/^sql: /; push @ignore_list_final, qr/^sql_storage: retrying/; push @ignore_list_final, qr/^sql flush: /; push @ignore_list_final, qr/^sql print/; push @ignore_list_final, qr/^sql begin transaction/; push @ignore_list_final, qr/^sql rollback/; push @ignore_list_final, qr/^mail_via_sql: /; push @ignore_list_final, qr/^CALLING SA check$/; push @ignore_list_final, qr/^calling SA parse,/; push @ignore_list_final, qr/^timer set to \d+/; push @ignore_list_final, qr/^query_keys/; push @ignore_list_final, qr/^find_or_save_addr: /; push @ignore_list_final, qr/^header: /; push @ignore_list_final, qr/^DO_QUARANTINE, /; push @ignore_list_final, qr/^DEBUG_ONESHOT: /; push @ignore_list_final, qr/^TempDir::/; push @ignore_list_final, qr/^check_mail_begin_task: /; push @ignore_list_final, qr/^program: .*?(anomy|altermime|disclaimer).*? said: /; # log_level 2 push @ignore_list_final, qr/^body (?:type|hash): /; push @ignore_list_final, qr/^\d+\.From: <.*>, \d+.Mail_From:/; push @ignore_list_final, qr/^The amavisd daemon is (?:apparently )?not running/; push @ignore_list_final, qr/^rw_loop/; push @ignore_list_final, qr/^[SL]MTP[><]/; push @ignore_list_final, qr/^[SL]MTP response for/; push @ignore_list_final, qr/^dsn:/i, # DSN or dsn push @ignore_list_final, qr/^enqueue: /; push @ignore_list_final, qr/^write_header: /; push @ignore_list_final, qr/^banned check: /; push @ignore_list_final, qr/^child_finish_hook/; push @ignore_list_final, qr/^inspect_dsn:/; push @ignore_list_final, qr/^client IP address unknown/; push @ignore_list_final, qr/^final_destiny/; push @ignore_list_final, qr/^one_response_for_all/; push @ignore_list_final, qr/^headers CLUSTERING/; push @ignore_list_final, qr/^notif=/; push @ignore_list_final, qr/^\(about to connect/; push @ignore_list_final, qr/^Original mail size/; push @ignore_list_final, qr/^TempDir removal/; push @ignore_list_final, qr/^Issued a new file name/; push @ignore_list_final, qr/^starting banned checks/; push @ignore_list_final, qr/^skip admin notification/; push @ignore_list_final, qr/^do_notify_and_quarantine - done/; push @ignore_list_final, qr/^do_[a-zA-Z]+.* done$/i; push @ignore_list_final, qr/^Remote host presents itself as:/; push @ignore_list_final, qr/^connect_to_ldap/; push @ignore_list_final, qr/^connect_to_sql: trying /; push @ignore_list_final, qr/^ldap begin_work/; push @ignore_list_final, qr/^Connecting to LDAP server/; push @ignore_list_final, qr/^loaded base policy bank/; push @ignore_list_final, qr/^\d+\.From:/; push @ignore_list_final, qr/^Syslog (retries|warnings)/; push @ignore_list_final, qr/^smtp connection cache/; push @ignore_list_final, qr/^smtp cmd> /; push @ignore_list_final, qr/^smtp session/; push @ignore_list_final, qr/^Ignoring stale PID file/; push @ignore_list_final, qr/^mime_decode_preamble/; push @ignore_list_final, qr/^doing banned check for/; push @ignore_list_final, qr/^open_on_specific_fd/; push @ignore_list_final, qr/^reparenting /; push @ignore_list_final, qr/^Issued a new pseudo part: /; push @ignore_list_final, qr/^run_command: /; push @ignore_list_final, qr/^result line from file/; push @ignore_list_final, qr/^Charging /; push @ignore_list_final, qr/^check_for_banned /; push @ignore_list_final, qr/^Extracting mime components$/; push @ignore_list_final, qr/^response to /; push @ignore_list_final, qr/^File-type of /; push @ignore_list_final, qr/^Skip admin notification, /; push @ignore_list_final, qr/^run_av: /; push @ignore_list_final, qr/^string_to_mime_entity /; push @ignore_list_final, qr/^ndn_needed=/; push @ignore_list_final, qr/^sending RCPT TO:/; push @ignore_list_final, qr/^decode_parts: /; push @ignore_list_final, qr/^decompose_part: /; push @ignore_list_final, qr/^setting body type: /; push @ignore_list_final, qr/^mime_decode_epilogue: /; push @ignore_list_final, qr/^string_to_mime_entity: /; push @ignore_list_final, qr/^at the END handler: /; push @ignore_list_final, qr/^Amavis::.* called$/; push @ignore_list_final, qr/^Amavis::.* close,/; push @ignore_list_final, qr/^dkim: /; # XXX provide stats push @ignore_list_final, qr/^collect banned table/; push @ignore_list_final, qr/^collect_results from/; push @ignore_list_final, qr/^blocking contents category is/; push @ignore_list_final, qr/^running file\(/; push @ignore_list_final, qr/^Found av scanner/; push @ignore_list_final, qr/^Found myself/; push @ignore_list_final, qr/^mail_via_smtp/; push @ignore_list_final, qr/^switch_to_client_time/; push @ignore_list_final, qr/^parse_message_id/; push @ignore_list_final, qr/^parse_received: /; push @ignore_list_final, qr/^parse_ip_address_from_received: /; push @ignore_list_final, qr/^fish_out_ip_from_received: /; push @ignore_list_final, qr/^Waiting for the process \S+ to terminate/; push @ignore_list_final, qr/^Valid PID file \(younger than sys uptime/; push @ignore_list_final, qr/^Sending SIG\S+ to amavisd/; push @ignore_list_final, qr/^Can't send SIG\S+ to process/; push @ignore_list_final, qr/^killing process/; push @ignore_list_final, qr/^no need to kill process/; push @ignore_list_final, qr/^process .* is still alive/; push @ignore_list_final, qr/^Daemon \[\d+\] terminated by SIG/; push @ignore_list_final, qr/^storage and lookups will use .* to SQL/; push @ignore_list_final, qr/^idle_proc, /; push @ignore_list_final, qr/^switch_to_my_time/; push @ignore_list_final, qr/^TempDir::strip: /; push @ignore_list_final, qr/^rmdir_recursively/; push @ignore_list_final, qr/^sending [SL]MTP response/; push @ignore_list_final, qr/^prolong_timer/; push @ignore_list_final, qr/^process_request:/; push @ignore_list_final, qr/^exiting process_request/; push @ignore_list_final, qr/^post_process_request_hook: /; push @ignore_list_final, qr/^SMTP session over/; push @ignore_list_final, qr/^updating snmp variables/; push @ignore_list_final, qr/^best_try_originator_ip/; push @ignore_list_final, qr/^mail checking ended: /; # log level 2 push @ignore_list_final, qr/^The amavisd daemon is already running/; push @ignore_list_final, qr/^AUTH not needed/; push @ignore_list_final, qr/^load: \d+ %, total idle/; push @ignore_list_final, qr/^policy protocol: [^=]+=\S+(?:,\S+)*$/; # allow "policy protocol: INVALID ..." later push @ignore_list_final, qr/^penpals: /; push @ignore_list_final, qr/^Not calling virus scanners, no files to scan in/; push @ignore_list_final, qr/^local delivery: /; push @ignore_list_final, qr/^run_as_subprocess: child process \S*: Broken pipe/; push @ignore_list_final, qr/^initializing Mail::SpamAssassin/; push @ignore_list_final, qr/^Error reading mail header section/; # seems to occur gen. due to perl getline() bug push @ignore_list_final, qr/^flatten_and_tidy_dir/; push @ignore_list_final, qr/^do_7zip: member/; push @ignore_list_final, qr/^Expanding \S+ archive/; push @ignore_list_final, qr/^files_to_scan:/; push @ignore_list_final, qr/^Unzipping p\d+/; push @ignore_list_final, qr/^writing mail text to SQL/; push @ignore_list_final, qr/^strip_tempdir/; push @ignore_list_final, qr/^no parts, file/; push @ignore_list_final, qr/^warnsender_with_pass/; push @ignore_list_final, qr/^RETURNED FROM SA check/; push @ignore_list_final, qr/^mime_traverse: /; push @ignore_list_final, qr/^do_spam: /; push @ignore_list_final, qr/^prepare_tempdir: /; push @ignore_list_final, qr/^check_header: /; push @ignore_list_final, qr/^skip admin notification/; push @ignore_list_final, qr/^do_executable: not a/; push @ignore_list_final, qr/^Skip spam admin notification, no administrators$/; push @ignore_list_final, qr/^skip banned check for/; push @ignore_list_final, qr/^is_outgoing /; push @ignore_list_final, qr/^NO Disclaimer/; push @ignore_list_final, qr/^Using \(\S+\) on file/; push @ignore_list_final, qr/^no anti-spam code loaded/; push @ignore_list_final, qr/^entered child_init_hook/; push @ignore_list_final, qr/^body type/; push @ignore_list_final, qr/^establish_or_refresh/; push @ignore_list_final, qr/^get_body_digest/; push @ignore_list_final, qr/^ask_daemon_internal/; push @ignore_list_final, qr/^Turning AV infection into a spam report, name already accounted for/; push @ignore_list_final, qr/^Calling virus scanners/; push @ignore_list_final, qr/^timer stopped after /; push @ignore_list_final, qr/^virus_presence /; push @ignore_list_final, qr/^cache entry /; push @ignore_list_final, qr/^generate_mail_id /; push @ignore_list_final, qr/^Load low precedence policybank/; push @ignore_list_final, qr/^warm restart on /; # XXX could be placed instartup info push @ignore_list_final, qr/^Signalling a SIGHUP to a running daemon/; push @ignore_list_final, qr/^Deleting db files /; push @ignore_list_final, qr/^address modified \(/; push @ignore_list_final, qr/^Request: AM\.PDP /; push @ignore_list_final, qr/^DSPAM result: /; push @ignore_list_final, qr/^bind to \//; push @ignore_list_final, qr/^ZMQ enabled: /; push @ignore_list_final, qr/^Inserting header field: X-Amavis-Hold: /; push @ignore_list_final, qr/^Decoding of .* failed, leaving it unpacked: /; # various forms of "Using ..." # more specific, interesting variants already captured: search "Using" push @ignore_list_final, qr/^Using \(.*\) on dir:/; push @ignore_list_final, qr/^Using [^:]+: \(built-in interface\)/; push @ignore_list_final, qr/^Using \(.*\): /; push @ignore_list_final, qr/: sleeping for /; push @ignore_list_final, qr/creating socket by /; # unanchored push @ignore_list_final, qr/\bRUSAGE\b/; push @ignore_list_final, qr/: Sending .* to UNIX socket/; } # Notes: # # - IN REs, always use /o flag or qr// at end of RE when RE uses unchanging interpolated vars # - In REs, email addresses may be empty "<>" - capture using *, not + ( eg. from=<[^>]*> ) # - See additional notes below, search for "Note:". # - XXX indicates change, fix or more thought required # Main processing loop # while (<>) { chomp; s/ +$//; next if $_ eq ''; $Logreporters::Reports::origline = $_; if ($Opts{'standalone'}) { next unless s/^[A-Z][a-z]{2} [ \d]\d \d{2}:\d{2}:\d{2} (?:<[^>]+> )?\S+ $Opts{'syslog_name'}(?:\[\d+\])?: (?:\[ID \d+ \w+\.\w+\] )?//o; } my $p1 = $_; my ($p2, $pid); my $action = "blocked"; # default action is blocked if not present in log # For now, ignore the amavis startup timing lines. Need to do this # before stripping out the amavis pid to differentiate these from the # scan timing reports next if ($p1 =~ /^TIMING/); my $linelen = length $p1; # Strip amavis process id-instance id, or release id if (($pid,$p2) = ($p1 =~ /^\(([^)]+)\) (.*)$/ )) { $p1 = $p2; } # Handle continuation lines. Continuation lines should be in order per PID, meaning line1, line2, line3, # but never line3, line1, line2. # # amavis log lines as chopped by sub write_log are exactly 980 characters long starting with '(' as in: # amavis[47061]: (47061-15) SPAM, etc ... # ^ <-----980-------------> # but this can be changed in amavis via $logline_maxlen. # There may also be the alert markers (!) and (!!) preceeding any continuation ellipsis. # # ... a continued line ... if ($p1 =~ s/^(\([!]{1,2}\))?\.\.\.//) { if (!exists($SaveLine{$pid})) { my $alert = $1; #printf "Unexpected continue line: \"%s\"\n", $p1; $SaveLine{$pid} = $alert || ''; } $SaveLine{$pid} .= $p1; next if $SaveLine{$pid} =~ s/\.\.\.$//; # next if line has more pieces } # this line continues ... if ($p1 =~ /\.\.\.$/ and $linelen == $logline_maxlen) { $p1 =~ s/\.\.\.$//; $SaveLine{$pid} = $p1; next; } if (exists($SaveLine{$pid})) { # printf "END OF SaveLine: %s\n", $SaveLine{$pid}; $p1 = delete $SaveLine{$pid}; } #if (length($p1) > 10000) { # printf "Long log entry %d chars: \"%s\"\n", length($p1), $p1; # next; #} next if ( # Place REs here that should ignore log lines otherwise caught below. # Some are located here historically, and need to be checked for candidates # to be relocated to ignore_list_final. ($p1 =~ /^do_ascii/) or ($p1 =~ /^Checking/) or ($p1 =~ /^header_edits_for_quar: /) or ($p1 =~ /^Not-Delivered/) or ($p1 =~ /^SpamControl/) or ($p1 =~ /^Perl/) or ($p1 =~ /^ESMTP/) or ($p1 =~ /^UTF8SMTP/) or ($p1 =~ /^LMTP /) or ($p1 =~ /^UTF8LMTP /) or ($p1 =~ /^(?:\(!+\))?(\S+ )?(?:FWD|SEND) from /) # log level 4 or ($p1 =~ /^(?:\(!+\))?(\S+ )?(?:ESMTP|FWD|SEND) via /) # log level 4 or ($p1 =~ /^tempdir being removed/) or ($p1 =~ /^do_notify_and_quar(?:antine)?: .*ccat/) or ($p1 =~ /^cached [a-zA-Z0-9]+ /) or ($p1 =~ /^loaded policy bank/) or ($p1 =~ /^p\.path/) or ($p1 =~ /^virus_scan: /) or ($p1 =~ /^Requesting (a |)process rundown after [0-9]+ tasks/) or ($p1 =~ /^Cached (virus|spam) check expired/) or ($p1 =~ /^pr(?:esent|ovid)ing full original message to scanners as/) # log level 2 or ($p1 =~ /^Actual message size [0-9]+ B(,| greater than the) declared [0-9]+ B/) or ($p1 =~ /^disabling DSN/) or ($p1 =~ /^Virus ([^,]+ )?matches [^,]+, sender addr ignored/) or ($p1 =~ /^release /) or ($p1 =~ /^adding SA score \S+ to existing/) or ($p1 =~ /^Maia:/) # redundant or ($p1 =~ /^AM\.PDP /) # this appears to be always have two spaces # because in amavisd::preprocess_policy_query() when $ampdp is # set, it will pass an unset $attr_ref->{'mail_id'} to do_log(1 or ($p1 =~ /^_(?:WARN|DIE):$/) # bug: empty _WARN|_DIE: http://marc.info/?l=amavis-user&m=121725098111422&w=2 # non-begin anchored or ($p1 =~ /result: clean$/) or ($p1 =~ /DESTROY called$/) or ($p1 =~ /email\.txt no longer exists, can't re-use it/) or ($p1 =~ /SPAM\.TAG2/) or ($p1 =~ /BAD-HEADER\.TAG2/) or ($p1 =~ /: Connecting to socket/) or ($p1 =~ /broken pipe \(don't worry\), retrying/) or ($p1 =~ /(?:Sending|on dir:) (?:CONT)?SCAN /) ); my ($ip, $from, $to, $key,, $reason, $item, $decoder, $scanner, $stage, $sectkey); # Coerce older "INFECTED" quarantined lines into "Blocked INFECTED", # to be processed in the Passed/Blocked section. if ($p1 =~ /^INFECTED.*, quarantine/) { $p1 = 'Blocked ' . $p1; } # SPAM entry occurs at kill level # SPAM-TAG entry occurs at log level 2, when spam header is inserted # log_level >= 2 || (log_level > 2 && syslog_priority=debug) my ($tagtype,$fromto,$isspam,$tags,$tests,$autolearn); # amavisd-new 2.7.0 changes SPAM-TAG to Spam-tag and its log_level to 3 if (($tagtype,$fromto,$isspam,$tags,$tests,$autolearn) = ($p1 =~ /^((?i:SPAM(?:-TAG)?)), (.*), (Yes|No), score=[-+x\d.]+(.*) tests=\[([^\]]*)](?:, autolearn=(\w+))?/) or ($tagtype,$fromto,$isspam,$tags,$tests) = ($p1 =~ /^((?i:SPAM(?:-TAG)?)), (.*), (Yes|No), hits=[-+x\d.]+(.*) tests=(.*)(?:, quarantine )?/)) { #TD SPAM, -> , Yes, score=17.709 tag=-10 tag2=6.31 kill=6.31 tests=[AWL=-0.678, BAYES_99=4], autolearn=spam, quarantine Cc4+GUJhgpqh (spam-quarantine) #TD SPAM, -> , Yes, score=21.161 tag=x tag2=8.15 kill=8.15 tests=[BAYES_99=2.5, FORGED_RCVD_HELO=0.135], autolearn=no, quarantine m6lWPoTGJ2O (spam-quarantine) #TD SPAM, -> , Yes, score=17.887 tag=-10 tag2=6.31 kill=6.31 tests=[BAYES_99=4], autolearn=spam, quarantine VFYjDOVTW4zd (spam-quarantine) #TD SPAM-TAG, -> , No, score=-0.069 tagged_above=-10 required=6.31 tests=[BAYES_00=-2.599, FROM_ENDS_IN_NUMS=2.53] #TD SPAM-TAG, -> , No, score=-1.294 required=8.15 tests=[BAYES_00=-2.599, FROM_LOCAL_HEX=1.305] # pre 2.3.3 #TD SPAM-TAG, -> , Yes, hits=6.159 tagged_above=-999 required=3.4 tests=BAYES_99=3.5, FUZZY_CPILL=0.518, HTML_MESSAGE=0.001, URIBL_WS_SURBL=2.14 #TD SPAM, -> , Yes, hits=8.1 tag1=-999.0 tag2=7.0 kill=7.0 tests=MANGLED_TAKE, UPPERCASE_25_50, quarantine spam-14156-09 (maia-spam-quarantine) $Totals{'tagged'}++ if uc($tagtype) eq 'SPAM-TAG'; if ($tests) { my $type = $isspam =~ /^Y/ ? 'Spam' : 'Ham'; # Note: A SPAM line may be followed by an almost identical SPAM-TAG line. To avoid double counting, # maintain a list of (abbreviated) SPAM tag lines keyed by pid. Since pid's are recycled, # maintain an approximation of uniqueness by combining several components from the log # line (we can't use the date information, as in logwatch, it is not present). # XXX: It is safe to delete an entry when the final Passed/Block line occurs #TD SPAM, -> , Yes, score=34.939 tag=x tag2=6.31 kill=6.31 tests=[DATE_IN_FUTURE_03_06=1.961], autolearn=disabled #TD SPAM-TAG, -> , Yes, score=34.939 required=6.31 tests=[DATE_IN_FUTURE_03_06=1.961] #TD SPAM, -> tod@sample.net>, Yes, score=31.565 tag=x tag2=6.9 kill=6.9 tests=[AV:Sanesecurity.Phishing.Bank.2666.UNOFFICIAL=4.1, AV:Sanesecurity.Phishing.Bank.2666.UNOFFICIAL=4.1, BAYES_99=4, DCC_CHECK=4, DIGEST_MULTIPLE=0.001, FORGED_MUA_OUTLOOK=3.116, FORGED_OUTLOOK_HTML=0.001, FORGED_OUTLOOK_TAGS=0.001, HTML_MESSAGE=0.001, L_AV_SS_Phish=5, MIME_HTML_ONLY=1.457, NORMAL_HTTP_TO_IP=0.001, RAZOR2_CF_RANGE_51_100=2, RAZOR2_CF_RANGE_E4_51_100=1.5, RAZOR2_CF_RANGE_E8_51_100=1.5, RAZOR2_CHECK=3, RDNS_NONE=0.1, URIBL_PH_SURBL=1.787] autolearn=spam my $tagstr = $fromto . '/' . $isspam . '/' . $tests; if (uc($tagtype) eq 'SPAM-TAG' and exists $spamtags{$pid}) { next if ($spamtags{$pid} eq $tagstr); } $spamtags{$pid} = $tagstr; #for (split /=[^,]+(?:, +|$)/, $tests) # amavis < 2.6.2 would double list AV names when using # @virus_name_to_spam_score_maps. my @unique_tests = unique_list (split /, +/, $tests); for (@unique_tests) { # skip possible trailing junk ("quarantine, ...") when older non-bracked tests=xxx is used next if ! /[^=]+=[\-.\d]+/; my ($id,$val) = split /=/; if ($id =~ /^BAYES_\d+$/) { $Counts{'bayes'}{$id}++ if ($Collecting{'bayes'}); } if ($Opts{'sarules'}) { if ($id eq 'DKIM_POLICY_SIGNSOME') { $val = 0 } elsif ($id eq 'AWL') { $val = '-' } $Counts{'sarules'}{$type}{sprintf "%6s %s", $val, $id}++; } } # Handled below #autolearn= is available only at ll>=3 or SPAM messages; so ham doesn't naturally occur here # SA 2.5/2.6 : ham/spam/no # SA 3.0+ : ham/spam/no/disabled failed/unavailable #$Counts{'autolearn'}{$type}{$autolearn}++ if ($Opts{'autolearn'}); } } # Passed or Blocked elsif ($p1 =~ /^(Passed|Blocked)(.*)/) { $action = lcfirst $1; ($p1 = $2) =~ s/^\s+//; $p1 =~ s/^,/CLEAN,/; # canonicalize older log entries #print "P1: \"$p1\"\n"; # amavis 20030616p10-5 #TD Passed, -> , Message-ID: <652.44494541@example.com>, Hits: 4.377 #TD Passed, -> , Message-ID: , Hits: - #TD Passed, -> , quarantine IJHkgliCm2Ia, Message-ID: <20080307140552.16E127641E@example.com>, Hits: 0.633 #TD Passed CLEAN, [10.0.0.1] [10.0.0.1] -> , Message-ID: <2qxz191@example.com>, mail_id: w4DHD8, Hits: -2.599, size: 3045, queued_as: 2056, 2664 ms #TD Passed CLEAN, [10.0.0.1] [10.0.0.1] -> , Message-ID: <2qxz191@example.com>, mail_id: w4DHD8, Hits: -2.541-3, size: 3045, queued_as: 2056, 2664 ms #TD Blocked SPAM, [10.0.0.1] [192.168.0.1] -> , quarantine: spam-EzEbE9W, Message-ID: <117894@example.com>, mail_id: EzEbE9W, Hits: 6.364, size: 16493, 6292 ms #TD Blocked SPAM, LOCAL [10.0.0.1] [10.0.0.2] -> , quarantine: spam-EzEbE9W, Message-ID: <110394@example.com>, mail_id: EzEbE9W, Hits: 6.364, size: 16493, 6292 ms #TD Blocked SPAM, [IPv6:2001:630:d0:f102:230:48ff:fe77:96e] [192.168.0.1] -> , quarantine: spam-EzEbE9W, Message-ID: <11780394@example.com>, mail_id: EzEbE9W, Hits: 6.364, size: 16493, 6292 ms #TD Passed SPAMMY, ORIGINATING/MYNETS LOCAL [10.0.0.1] [10.0.0.1] -> ,, quarantine: spam-EzEbE9W, Message-ID: <11780394@example.com>, mail_id: EzEbE9W, Hits: 6.364, size: 16493, 6292 ms #TD Blocked SPAM, B-BANK/C-BANK/B-BANK [10.0.0.1] [10.0.0.1] -> , quarantine: spam-EzEbE9W, Message-ID: <11780394@example.com>, mail_id: EzEbE9W, Hits: 6.364, size: 16493, 6292 ms #TD Blocked SPAM, [10.0.0.1] [10.0.0.1] -> , quarantine: spam-AV49p5, Message-ID: <1.007@sample.net>, mail_id: AV49p5, Hits: 7.487, size: 27174, 4406 ms #TD Passed SPAM, MYNETS -> , quarantine: spam-V3Wq, Message-ID: <220.1B@example.com>, mail_id: V3Wq, Hits: 7, size: 8838, queued_as: C63EC, 18 ms #TD Passed SPAM, <> -> <"fred).flintstone"@domain.tld>, Message-ID: <200801180104.CAA23669@aserver.sub.adomain.tld>, mail_id: 6AzQ1g0l5RgP, Hits: 9.061, size: 5555, queued_as: C1840506CB8, 8766 ms #TD Blocked INFECTED (HTML.Phishing.Bank-43), [198.168.0.1] [10.0.0.1] -> , quarantine: virus-SCwJcs, Message-ID: <509@acm.org>, mail_id: SCwJcs, Hits: -, size: 4134, 3721 ms #TD Blocked INFECTED (Trojan.Downloader.Small-9993), LOCAL [10.0.0.2] [10.0.0.2] -> , quarantine: virus-SCwJcs, Message-ID: <9009@acm.org>, mail_id: SCwJcs, Hits: -, size: 4134, 3721 ms #TD Blocked BANNED (multipart/report | message/partial,.txt), [192.168.0.1] [10.0.0.2] <> -> , quarantine: virus-SCwJcs, Message-ID: <509@acm.org>, mail_id: SCwJcs, Hits: -, size: 4134, 3721 ms #TD Blocked BANNED (multipart/report | message/partial,.txt), LOCAL [192.168.0.1] [10.0.0.2] <> -> , quarantine: virus-SCwJcs, Message-ID: <509@acm.org>, mail_id: SCwJcs, Hits: -, size: 4134, 3721 ms #TD Blocked BANNED (multipart/mixed | application/octet-stream,.asc,=?iso-8859-1?Q?FTP=5FFile=5F (1)=File(1).reg), [192.168.0.0] [192.168.0.0] -> , quarantine: virus-SCwJcs, Message-ID: <509@acm.org>, mail_id: SCwJcs, Hits: -, size: 4134, 3721 ms #TD Blocked BANNED (multipart/related | application/zip,.zip,card.zip | .exe,.exe-ms,Card.exe), [10.0.0.2] [10.0.0.2] -> , quarantine: banned-9OXm4Q3ah, Message-ID: <08517$@from>, mail_id: 9OXm4Q3ah, Hits: -, size: 2366, 3803 ms #TD Passed BAD-HEADER, [192.168.0.1] [10.0.0.2] -> , quarantine: virus-SCwJcs, Message-ID: , mail_id: SCwJcs, Hits: 2.54 size: 4134, 3721 ms #TD Passed BAD-HEADER, LOCAL [192.168.0.1] [10.0.0.2] -> , quarantine: virus-SCwJcs, Message-ID: , mail_id: SCwJcs, Hits: 3.2 size: 4134, 3721 ms #TD Passed BAD-HEADER, MYNETS AM.PDP [127.0.0.1] [127.0.0.1] -> , quarantine: virus-SCwJcs, Message-ID: , mail_id: SCwJcs, Hits: 1.2 size: 4134, 3721 ms #TD Passed BAD-HEADER, ORIGINATING/MYNETS LOCAL [10.0.0.1] [10.0.0.1] -> ,,, quarantine: virus-SCwJcs, Message-ID: , mail_id: SCwJcs, Hits: -, size: 4134, 3721 ms #TD Passed BAD-HEADER, [10.0.0.1] [10.0.0.2] -> , quarantine: badh-lxR, Message-ID: <7fm@example.com>, mail_id: lxR, Hits: -2.292, size: 422, queued_as: E3B, 981 ms #TD Passed UNCHECKED, MYNETS LOCAL [192.168.0.1] [192.168.0.1] -> Message-ID: <002e01c759c7$5de437b0$0a02a8c0@somehost>, mail_id: 7vtR-7BAvHZV, Hits: -, queued_as: B5420C2E10, 6585 ms #TD Blocked MTA-BLOCKED, LOCAL [192.168.0.1] [192.168.0.2] -> , Message-ID: <438548@example.com>, mail_id: tfgTCiyvFw, Hits: -2.54, size: 4895, 31758 ms #TD Blocked OVERSIZED, LOCAL [10.0.0.1] [10.0.0.1] -> , Message-ID: <435@example.com>, mail_id: tfTivFw, Hits: -2.54, size: 444444895, 31758 ms #TD Blocked OTHER, LOCAL [10.0.0.1] [10.0.0.1] -> , Message-ID: <435@example.com>, mail_id: tfTivFw, Hits: -2.54, size: 495, 31758 ms #TD Blocked TEMPFAIL, [10.0.0.2] [10.0.0.1] -> , Message-ID: <200703302301.9f1899470@example.com>, mail_id: bgf52ZCNbPo, Hits: -2.586, 3908 ms #2.3.1 #<>,,Passed,Hits=-3.3,Message-ID=<200506440.1.sample.net>,Size=51458 #20030616p10-5 #Not-Delivered, -> , quarantine spam-ea32770-03, Message-ID: , Hits: 9.687 # malwarepassed, malwareblocked # xxx very old # Virus found - quarantined| #amavisd-new-20030616 # INFECTED (JS/IllWill-A), -> , quarantine virus-20040811-207-0-03, Message-ID: <0440.5577-101@sample.net>, Hits: - # INFECTED (Exploit.HTML.IFrame, Worm.SomeFool.P), -> ,, quarantine qiO2ZG4K, Message-ID: <200608.5A5@mail.example.com>, Hits: - #XXX (?:(Passed|Blocked) )?INFECTED \(([^\)]+)\),[A-Z .]*(?: \[($re_IP)\])?(?: \[$re_IP\])* [<(]([^>)]*)[>)] -> [(<]([^(<]+)[)>]/o )) #XXX elsif (($action, $key, $ip, $from, $to) = ( $p1 =~ /^(?:Virus found - quarantined|(?:(Passed|Blocked) )?INFECTED) \(([^\)]+)\),[A-Z .]*(?: \[($re_IP)\])?(?: \[$re_IP\])* [<(]([^>)]*)[>)] -> [(<]([^(<]+)[(>]/o )) # the first IP is the envelope sender. if ($p1 !~ /^(CLEAN|SPAM(?:MY)?|INFECTED \(.*?\)|BANNED \(.*?\)|BAD-HEADER(?:-\d)?|UNCHECKED|MTA-BLOCKED|OVERSIZED|OTHER|TEMPFAIL)(?: \{[^}]+\})?, ([^[]+ )?(?:([^<]+) )?[<(](.*?)[>)] -> ([(<].*?[)>]), (?:.*Hits: ([-+.\d]+))(?:.* size: (\d+))?(?:.* autolearn=(\w+))?/) { inc_unmatched('passblock'); next; } my $trigger; my ($ccatmajor, $pbanks, $ips, $from, $reciplist, $hits, $size, $autolearn) = ($1, $2, $3, $4, $5, $6, $7, $8); $Totals{'bytesscanned'} += $size if defined $size; #print "ccatmajor: \"$ccatmajor\", pbanks: \"$pbanks\"\n"; if ($ccatmajor =~ /^(INFECTED|BANNED) \((.*)\)$/) { ($ccatmajor, $trigger) = ($1, $2); #print "\tccatmajor: \"$ccatmajor\", trigger: \"$trigger\"\n"; } $ccatmajor =~ s/(BAD-HEADER)-\d/$1/; # strip amavis 2.7's [:ccat|minor] BAD-HEADER sub-classification $sectkey = $ccatmajor_to_sectkey{$ccatmajor} . $action; $Totals{$sectkey}++; # Not checked by spamassassin, due to $sa_mail_body_size_limit or @bypass_spam_checks_maps if ($hits eq '-') { # Don't increment sabypassed for INFECTED (SA intentionally not called) unless ($ccatmajor eq 'INFECTED') { # The following order is used, the first condition met decides the outcome: # 1. a virus is detected: mail is considered infected; # 2. contains banned name or type: mail is considered banned; # 3. spam level is above kill level for at least one recipient, or a sender is blacklisted: mail is considered spam; # 4. bad (invalid) headers: mail is considered as having a bad header. # Priority: VIRUS BANNED UNCHECKED SPAM SPAMMY BADH OVERSIZED MTA CLEAN $Totals{'sabypassed'}++; } } else { if ($Collecting{'spamscores'}) { no re 'taint'; if ($hits =~ /^(-?[.\d]+)([-+])([.\d]+)$/) { $hits = eval $1.$2.$3; # untaint $hits, to sum $1 and $3 values } # SA not called for ccats INFECTED and BANNED (Hits: -). # UNCHECKED may have a score, so we can't distinguish Ham from Spam push @{$SpamScores{$ccatmajor_to_spamham{$ccatmajor}}}, $hits; } } # autolearn is available here only if enabled in amavis template if ($autolearn ne '' and $Opts{'autolearn'}) { #if ($autolearn ne '' and ($ccatmajor eq 'SPAM' or $ccatmajor eq 'CLEAN')) { # SA 2.5/2.6 : ham/spam/no # SA 3.0+ : ham/spam/no/disabled/failed/unavailable # printf "INC: autolearn: %s, %s: %d\n", $ccatmajor eq 'SPAM' ? 'Spam' : 'Ham', $autolearn, $Opts{'autolearn'};; # Priorities other than SPAM will be considered HAM for autolearn stats $Counts{'autolearn'}{$ccatmajor eq 'SPAM' ? 'Spam' : 'Ham'}{$autolearn}++; } # p0f fingerprinting if (exists $p0ftags{$pid}) { my ($ip,$score,$os) = split(/\//, $p0ftags{$pid}); $Counts{'p0f'}{ucfirst($ccatmajor_to_spamham{$ccatmajor})}{$os}{$ip}++; #print "Deleting p0ftag: $pid\n"; delete $p0ftags{$pid}; } next unless ($Collecting{$sectkey}); # cleanpassed never gets here... # prefer xforward IP if it exists # $ip_a => %a original SMTP session client IP address (empty if unknown, e.g. no XFORWARD) # $ip_e => %e best guess of the originator IP address collected from the Received trace my ($ip_a, $ip_e) = split(/ /, $ips, 2); $ip = $ip_a ? $ip_a : $ip_e; $ip =~ s/[[\]]//g; #print "ip: \"$ip\", ip_a: \"$ip_a\", ip_e: \"$ip_e\", from: \"$from\", reciplist: \"$reciplist\"; hits: \"$hits\"\n"; $ip = '*unknown IP' if ($ip eq ''); $from = '<>' if ($from eq ''); # Show first recipient only, or all my @recips = split /,/, $reciplist; @recips = map { /^<(.+)>$/ } @recips; # show only first recipient $to = lc ($Opts{'first_recip_only'} ? $recips[0] : "@recips"); if ($ccatmajor eq 'INFECTED') { # $ccatmajor: INFECTED malwarepassed, malwareblocked $Counts{$sectkey}{$trigger}{$to}{$ip}{$from}++; } elsif ($ccatmajor eq 'BANNED') { # $ccatmajor: BANNED bannednamepassed, bannednameblocked $Counts{$sectkey}{$to}{$trigger}{$ip}{$from}++; } else { # $ccatmajor: CLEAN | SPAM{MY} | BAD-HEADER | UNCHECKED | MTA-BLOCKED | OVERSIZED | OTHER | TEMPFAIL # cleanpassed, cleanblocked, spampassed, spamblocked, badheaderpassed, badheaderblocked # uncheckedpassed, uncheckblocked, mtapassed, mtablocked, oversizedpassed, oversizedblocked # otherpassed, otherblocked, tempfailpassed, tempfailblocked $Counts{$sectkey}{$to}{$ip}{$from}++; } # old... #XXX elsif (($action, $item, $ip, $from, $to) = ( $p1 =~ /^(?:(Blocked|Passed) )?BANNED (?:name\/type )?\((.+)\),[^[]*(?: \[($re_IP)\])?(?: \[$re_IP\])* [<(]([^>)]*)[>)] -> [(<]([^(<]+)[(>]/o)) #XXXX elsif (($action, $ip, $from, $to) = ( $p1 =~ /^(?:(Passed|Blocked) )?UNCHECKED,[^[]*(?: \[($re_IP)\])?(?: \[$re_IP\])* [<(]([^>)]*)[>)] -> [(<]([^>)]*)[)>]/o )) #XXX elsif (($action, $ip, $from, $to) = ( $p1 =~ /^(?:(Passed|Blocked) )?TEMPFAIL,[^[]*(?: \[($re_IP)\])?(?: \[$re_IP\])* [<(]([^>)]*)[>)] -> [(<]([^>)]*)[)>]/o )) #XXX elsif (($action, $ip, $from, $to) = ( $p1 =~ /^(?:(Blocked|Passed) )?BAD-HEADER,[^[]*(?: \[($re_IP)\])?(?: \[$re_IP\])* [(<]([^>)]*)[)>](?: -> [(<]([^>)]+)[)>])[^:]*/o )) # amavis 2.3.1 #BAD-HEADER, <> -> , Message-ID: <200506440.1.sample.net>, Hits=-3.3 tag1=3.0 tag2=7.5 kill=7.5, tests=ALL_TRUSTED=-3.3, [10.0.0.1] } # end Passed or Blocked # MAIA elsif ($p1 =~ /^FAKE SENDER, ([^:]+): ($[^,]+), (.*)$/o) { #TD FAKE SENDER, SPAM: 192.168.0.1, bogus@example.com $Totals{'fakesender'}++; next unless ($Collecting{'fakesender'}); $Counts{'fakesender'}{$1}{$2}{$3}++; } elsif ($p1 =~ /^p\d+ \d+(?:\/\d+)* Content-Type: ([^,]+)(?:, size: [^,]+, name: (.*))?/) { my ($ts, $name) = ($1, $2); #TD p006 1 Content-Type: multipart/mixed #TD p008 1/1 Content-Type: multipart/signed #TD p001 1/1/1 Content-Type: text/plain, size: 460 B, name: #TD p002 1/1/2 Content-Type: application/pgp-signature, size: 189 B, name: #TD p002 1/2 Content-Type: application/octet-stream, size: 3045836 B, name: abc.pdf next unless ($Collecting{'contenttype'}); my ($type, $subtype) = $ts !~ '""' ? split /\//, $ts : ('unspecified', 'unspecified'); $name = '' if !defined $name or $name =~ /^\s*$/; $Counts{'contenttype'}{$type}{$subtype}{$name}++; } # LMTP/SMTP connection # NOTE: no longer used. size data now being obtained from Passed/Block line, as size info may not be available here #elsif (my ($size) = ($p1 =~ /^[LS]MTP:(?:\[$re_IP\])?:\d+ [^:]+: [<(](?:.*?)[>)] -> \S+ (?:SIZE=(\d+))?.*?Received: / )) { elsif ($p1 =~ /^[LS]MTP ?:/) { #TD LMTP::10024 /var/spool/amavis/tmp/amavis-20070119T144757-09086: -> SIZE=1000 Received: from mail.sample.net ([127.0.0.1]) by localhost (mail.sample.net [127.0.0.1]) (amavisd-new, port 10024) with LMTP for ; Fri, 19 Jan 2007 15:41:45 -0800 (PST) #TD SMTP:[127.0.0.1]:10024 /var/spool/amavis/tmp/amavis-20070119T144757-09086: -> , SIZE=2500000 Received: from mail.sample.net ([127.0.0.1]) by localhost (mail.sample.net [127.0.0.1]) (amavisd-new, port 10024) with LMTP for ; Fri, 19 Jan 2007 15:41:45 -0800 (PST) #TD SMTP::10024 /var/lib/amavis/tmp/amavis-27-26927: -> Received: from localhost ([127.0.0.1]) by localhost (example.com [127.0.0.1]) (amavisd-new, port 10024) with SMTP for ; Sat, 7 Jun 2008 23:09:34 +0200 (CEST) #$Totals{'bytesscanned'} += $size if defined $size; } #(\S+) ([^[(]+)(.*)$ elsif ($p1 =~ /^OS_fingerprint: (\S+) ([-\d.]+) (\S+)(?: ([^[(]+|\[[^]]+\]))?/o) { #TD OS_fingerprint: 213.193.24.113 29.789 Linux 2.6 (newer, 1) (up: 1812 hrs), (distance 14, link: ethernet/modem) #TD OS_fingerprint: 10.47.2.155 -1.312 MYNETWORKS # Note: safe to delete entry when the final Passed/Block line occurs if ($Collecting{'p0f'}) { my ($genre,$vers) = ($3,$4); #print "p0f:\t$3\t\t$vers\n"; if ($genre eq 'Windows') { local($1); $vers = $1 if $vers =~ /^(\S+) /; $genre .= ' ' . $vers; } elsif ($genre eq 'UNKNOWN') { $genre = 'Unknown'; } $p0ftags{$pid} = join('/', $1,$2,$genre); #print "Added PID: $pid, $p0ftags{$pid}\n"; } } elsif ( ($reason) = ( $p1 =~ /^BAD HEADER from [^:]+: (.+)$/) or ($reason) = ( $p1 =~ /check_header: \d, (.+)$/)) { # When log_level > 1, provide additional header or MIME violations # amavisd < 2.4.0, log_level >= 1 #TD BAD HEADER from : Improper use of control character (char 0D hex) in message header 'Received': Received: example.com[10.0.0.1\r] #TD BAD HEADER from : Non-encoded 8-bit data (char F7 hex) in message header 'Subject': Subject: \367\345\370\361 \344\351\351\362\345\365\n #TD BAD HEADER from : MIME error: error: part did not end with expected boundary #TD BAD HEADER from (bulk ) : Non-encoded 8-bit data (char E6 hex) in message header 'Subject': Subject: spam\\346ham\\n #TD BAD HEADER from (list) : MIME error: error: part did not end with expected boundary # amavisd >= 2.4.3, log_level >= 2 #TD check_header: 2, Non-encoded 8-bit data (char AE hex): Subject: RegionsNet\\256 Online Banking\\n #TD check_header: 2, Non-encoded 8-bit data (char E1 hex): From: "any user" \\n #TD check_header: 3, Improper use of control character (char 0D hex): Content-type: text/html; charset=i... #TD check_header: 8, Duplicate header field: "Reply-To" #TD check_header: 8, Duplicate header field: "Subject" #TD check_header: 4, Improper folded header field made up entirely of whitespace (char 09 hex): X-Loop-Detect: 3\\n\\t\\n #TD check_header: 4, Improper folded header field made up entirely of whitespace: Received: ...8 ; Thu, 10 Jan 2008 03:41:35 +0100\\n\\t \\n my $subreason; if ($reason =~ /^(.*?) \((char \S+ hex)\)(.*)$/) { $reason = $1; my ($char,$sub) = ($2,$3); $sub =~ s/^in message header '[^:]+': //; $sub =~ s/^: //; $subreason = "$char: $sub"; } elsif ($reason =~ /^(Improper folded header field made up entirely of whitespace):? (.*)/) { $reason = $1; $subreason = $2; } elsif ($reason =~ /^(Duplicate header field): "(.+)"$/) { $reason = $1; $subreason = $2; } elsif ($reason =~ /^(MIME error): (?:error: )?(.+)$/) { $reason = $1; $subreason = $2; } $Totals{'badheadersupp'}++; next unless ($Collecting{'badheadersupp'}); $Counts{'badheadersupp'}{$reason}{$subreason}++; } elsif ($p1 =~ /^truncating a message passed to SA at/) { #TD truncating a message passed to SA at 431018 bytes, orig 1875912 $Totals{'truncatedmsg'}++; } elsif ($p1 =~ /: spam level exceeds quarantine cutoff level/ or $p1 =~ /: cutoff, blacklisted/) { #TD do_notify_and_quarantine: spam level exceeds quarantine cutoff level 20 #TD do_notify_and_quarantine: cutoff, blacklisted $Totals{'spamdiscarded'}++; } elsif ( $p1 =~ /^spam_scan: (.*)$/) { #if ($1 =~ /^not wasting time on SA, message longer than/ ) { #TD spam_scan: not wasting time on SA, message longer than 409600 bytes: 1326+4115601 # this causes duplicate counts, and the subsequent Passed/Blocked log line # will have "Hits: -," whereby sabypassed is incremented. #$Totals{'sabypassed'}++; #} # ignore other spam_scan lines } # WARN: elsif ( ($reason) = ( $p1 =~ /^WARN: MIME::Parser error: (.*)$/ )) { # WARN: MIME::Parser error: unexpected end of header $Totals{'mimeerror'}++; next unless ($Collecting{'mimeerror'}); $Counts{'mimeerror'}{$reason}++; } elsif ($p1 =~ /^WARN: address modified \((\w+)\): <(.*?)> -> <(.*)>$/) { #TD WARN: address modified (sender): -> #TD WARN: address modified (recip): -> #TD WARN: address modified (recip): -> #TD WARN: address modified (recip): <"test@example.com"@> -> <"teszt@example.com"> #TD WARN: address modified (sender): -> <"fr\344om"@sample.net> $Totals{'warningaddressmodified'}++; next unless ($Collecting{'warningaddressmodified'}); $Counts{'warningaddressmodified'}{$1 eq 'sender' ? "Sender address" : "Recipient address"}{"$2 -> $3"}++; } # NOTICE: elsif ($p1 =~ /^NOTICE: (.*)$/) { # uninteresting #TD NOTICE: reconnecting in response to: err=2006, HY000, DBD::mysql::st execute failed: MySQL server has gone away at (eval 71) line 166, line 4. next if ($1 =~ /^Disconnected from SQL server/); # redundant next if ($1 =~ /^do_search: trying again: LDAP_OPERATIONS_ERROR/); next if ($1 =~ /^reconnecting in response to: /); if ($1 =~ /^Not sending DSN, spam level ([\d.]+ )?exceeds DSN cutoff level/) { #TD NOTICE: Not sending DSN, spam level exceeds DSN cutoff level for all recips, mail intentionally dropped $Totals{'dsnsuppressed'}++; $Counts{'dsnsuppressed'}{'DSN cutoff exceeded'}++; } elsif ($1 =~ /^Not sending DSN to believed-to-be-faked sender/) { #TD NOTICE: Not sending DSN to believed-to-be-faked sender , mail containing VIRUS intentionally dropped $Totals{'dsnsuppressed'}++; $Counts{'dsnsuppressed'}{'Sender likely faked'}++; } elsif ($1 =~ /^DSN contains [^;]+; bounce is not bounc[ai]ble, mail intentionally dropped/) { $Totals{'dsnsuppressed'}++; $Counts{'dsnsuppressed'}{'Not bounceable'}++; } elsif ($1 =~ /^UNABLE TO SEND DSN to /) { #TD NOTICE: UNABLE TO SEND DSN to : 554 5.7.1 Failed, id=19838-01, from MTA([127.0.0.1]:10025): 554 5.7.1 : Recipient address rejected: Access denied $Totals{'dsnsuppressed'}++; $Counts{'dsnsuppressed'}{'Unable to send'}++; } elsif ($1 =~ /^Skipping (?:bad|extra) output from file\(1\)/) { #TD NOTICE: Skipping extra output from file(1): blah #TD NOTICE: Skipping bad output from file(1) at [1, p002], got: blah $Totals{'fileoutputskipped'}++; } elsif (($p1) = ($1 =~ /^Virus scanning skipped: (.*)$/)) { #TD NOTICE: Virus scanning skipped: Maximum number of files (1500) exceeded at (eval 57) line 1283, line 1501. $Totals{'virusscanskipped'}++; next unless ($Collecting{'virusscanskipped'}); $Counts{'virusscanskipped'}{strip_trace($p1)}++; } else { inc_unmatched('NOTICE'); next; } } # INFO: elsif ($p1 =~ /^INFO: (.*)$/) { next if ($1 =~ /^unfolded \d+ illegal all-whitespace continuation line/); next if ($1 =~ /^removed bare CR/); if ($1 =~ /^truncat(ed|ing)/) { #TD INFO: truncating long header field (len=2639): X-Spam-Report: =?iso-8859-1?Q?=0A=0A*__1=2E7_SUBJECT=5FENCODED=5FTWICE_Subject=3A_MIME_e?= =?iso-885... #TD INFO: truncated 1 header line(s) longer than 998 characters $Totals{'truncatedheader'}++; } elsif ( $1 =~ /^no existing header field 'Subject', inserting it/) { $Totals{'nosubject'}++; } elsif (my ($savers1, $savers2, $item) = ( $1 =~ /^(?:SA version: ([^,]+), ([^,]+), )?no optional modules: (.+)$/)) { #TD INFO: SA version: 3.1.8, 3.001008, no optional modules: DBD::mysql Mail::SpamAssassin::Plugin::DKIM Mail::SpamAssassin::Plugin::URIDetail Error next unless ($Opts{'startinfo'}); if ($savers1 ne '') { $StartInfo{'sa_version'} = "$savers1 ($savers2)"; } foreach my $code (split / /, $item) { $StartInfo{'Code'}{'Not loaded'}{$code} = ""; } } elsif (my ($name) = ( $1 =~ /^(unknown banned table name \S+), .+$/)) { #TD INFO: unknown banned table name 1, recip=r@example.com $Totals{'warning'}++; next unless ($Collecting{'warning'}); $Counts{'warning'}{ucfirst $name}++; } else { inc_unmatched('INFO'); next; } } elsif ( ($action,$reason,$from,$to) = ($p1 =~ /^DSN: NOTIFICATION: Action:([^,]+), ([^,]+), <(.*?)> -> <(.*?)>/)) { #TD DSN: NOTIFICATION: Action:failed, LOCAL 554 Banned, -> #TD DSN: NOTIFICATION: Action:delayed, LOCAL 454 Banned, -> $Totals{'dsnnotification'}++; next unless ($Collecting{'dsnnotification'}); $Counts{'dsnnotification'}{$action}{$reason}{"$from -> $to"}++; } elsif (($item, $from, $to) = ( $p1 =~ /^Quarantined message release(?: \([^)]+\))?: ([^ ]+) <(.*?)> -> (.+)$/) or ($item, $from, $to) = ( $p1 =~ /^Quarantine release ([^ ]+): overriding recips <([^>]*)> by (.+)$/)) { #TD Quarantine release arQcr95dNHaW: overriding recips by #TD Quarantined message release: hiyPJOsD2m9Z -> #TD Quarantined message release: hiyPJOsD2m9Z <> -> , # 2.6+ #TD Quarantined message release (miscategorized): Iu6+0u1voOA -> $Totals{'released'}++; next unless ($Collecting{'released'}); $from = '<>' if ($from eq ''); $to =~ s/[<>]//g; $Counts{'released'}{"\L$from"}{$to}{$item}++; } elsif ($p1 =~ /^Quarantine release ([^:]+): missing X-Quarantine-ID$/) { #TD Quarantine release 7ejEBC7MThSc: missing X-Quarantine-ID $Totals{'warningnoquarantineid'}++; next unless ($Collecting{'warningnoquarantineid'}); $Counts{'warningnoquarantineid'}{$1}++; } elsif ( ($stage,$reason) = ($p1 =~ /^Negative SMTP resp\S* +to ([^:]+): *(.*)$/)) { #TD Negative SMTP response to data-dot (): 550 5.7.1 Header Spam Rule 4 $Totals{'smtpresponse'}++; next unless ($Collecting{'smtpresponse'}); $Counts{'smtpresponse'}{'Negative response'}{$stage}{$reason}++; } elsif ( ($stage,$reason) = ($p1 =~ /^smtp resp to ([^:]+): *(.*)$/)) { #TD smtp resp to NOOP (idle 4799.4 s): 421 4.4.2 nops.overtops.org Error: timeout exceeded #TD smtp resp to MAIL (pip): 250 2.1.0 Ok $Totals{'smtpresponse'}++; next unless ($Collecting{'smtpresponse'}); $stage =~ s/ [\d.]+ s//; $Counts{'smtpresponse'}{'Response'}{$stage}{$reason}++; } elsif ( ($item) = ($p1 =~ /^response to RCPT TO for <([^>]*)>: "501 Bad address syntax"/)) { #TD response to RCPT TO for <""@example.com>: "501 Bad address syntax" $Totals{'badaddress'}++; next unless ($Collecting{'badaddress'}); $Counts{'badaddress'}{$item}++; } # do_unip: archive extraction elsif ($p1 =~ s/^do_unzip: \S+, //) { $Totals{'archiveextract'}++; next unless ($Collecting{'archiveextract'}); if ( $p1 =~ s/^\d+ members are encrypted, //) { #TD do_unzip: p003, 4 members are encrypted, none extracted, archive retained $Counts{'archiveextract'}{'Encrypted'}{$p1}++; } elsif ( $p1 =~ /^zero length members, archive retained/) { #TD do_unzip: p002, zero length members, archive retained $Counts{'archiveextract'}{'Empty member'}{''}++; } elsif ($p1 =~ s/^unsupported compr\. method: //) { #TD do_unzip: p003, unsupported compr. method: 99 $Counts{'archiveextract'}{'Unsupported compression'}{$p1}++; } else { $Counts{'archiveextract'}{'*unknown'}{$p1}++; } } # do_cabextract: archive extraction elsif ($p1 =~ s/^do_cabextract: //) { #TD do_cabextract: can't parse toc line: File size | Date Time | Name #TD do_cabextract: can't parse toc line: All done, no errors. $Totals{'archiveextract'}++; next unless ($Collecting{'archiveextract'}); if ($p1 =~ /^([^:]+):\s*(.*)/) { $Counts{'archiveextract'}{"\u$1"}{$2}++; } else { $Counts{'archiveextract'}{$p1}{''}++; } } elsif ($p1 =~ /^(?:\(!\) *)?SA TIMED OUT,/) { $Totals{'satimeout'}++; } elsif ($p1 =~ /^mangling (.*)$/) { $p1 = $1; if ($p1 =~ /^by (.+?) failed: (.+?), mail will pass unmodified$/) { #TD mangling by altermime failed: SomeText, mail will pass unmodified $Totals{'defangerror'}++; next unless ($Collecting{'defangerror'}); $Counts{'defangerror'}{$1}{$2}++; } # other mangle message skipped else { #TD mangling YES: 1 (orig: 1), discl_allowed=0, -> #TD mangling by built-in defanger: 1, next; } } elsif ($p1 =~ /^DEFANGING MAIL: (.+)$/) { # log_level 1 #TD DEFANGING MAIL: WARNING: possible mail bomb, NOT CHECKED FOR VIRUSES:\n Exceeded storage quota 5961070 bytes by d... #TD DEFANGING MAIL: WARNING: bad headers - Improper use of control character (char 0D hex): To: ,\\n\\t # could use instead... #do_log(1,"mangling by %s (%s) done, new size: %d, orig %d bytes", $actual_mail_mangle, $mail_mangle, $repl_size, $msginfo->msg_size); $Totals{'defanged'}++; next unless ($Collecting{'defanged'}); $Counts{'defanged'}{$1}++; } elsif ($p1 =~ /^PenPalsSavedFromKill [-.\d]+,/) { #TD PenPalsSavedFromKill 8.269-3.160, -> $Totals{'penpalsaved'}++; } # I don't know how many variants of time outs there are... I suppose we'll fix as we go elsif (($p1 =~ /^\(!+\)([^ ]*) is taking longer than \d+ s and will be killed/) or ($p1 =~ /^\(!+\)(.*) av-scanner FAILED: timed out/) or ($p1 =~ /^(?:\(!+\))?(.*): timed out/)) { #TD (!)/usr/local/bin/uvscan is taking longer than 10 s and will be killed #TD (!!)NAI McAfee AntiVirus (uvscan) av-scanner FAILED: timed out #TD ClamAV-clamd: timed out, retrying (1) #TD (!)Sophie: timed out, retrying (2) $Totals{'avtimeout'}++; next unless ($Collecting{'avtimeout'}); $Counts{'avtimeout'}{$1}++; } elsif (($p2) = ($p1 =~ /SMTP shutdown: (.*)$/)) { # log level -1 #TD SMTP shutdown: Error writing a SMTP response to the socket: Broken pipe at (eval 49) line 836, line 51. #TD SMTP shutdown: tempdir is to be PRESERVED: /var/amavis/tmp/amavis-20070704T095350-13145 strip_trace($p2); if ($p2 =~ /^tempdir is to be PRESERVED: (.*)\/([^\/]+)$/) { $Totals{'tmppreserved'}++; $Counts{'tmppreserved'}{$1}{$2}++ if ($Collecting{'tmppreserved'}); $p2 = "Preserved tempdir in $1"; } $Totals{'warningsmtpshutdown'}++; next unless ($Collecting{'warningsmtpshutdown'}); $Counts{'warningsmtpshutdown'}{ucfirst($p2)}++; } elsif (($p1 =~ /PRESERVING EVIDENCE in (.*)\/([^\/]+)$/) or ($p1 =~ /tempdir is to be PRESERVED: (.*)\/([^\/]+)$/)) { #TD (!)TempDir removal: tempdir is to be PRESERVED: /var/amavis/tmp/amavis-20080110T173606-05767 # log level -1 #TD PRESERVING EVIDENCE in /var/amavis/tmp/amavis-20070704T111558-14883 $Totals{'tmppreserved'}++; next unless ($Collecting{'tmppreserved'}); $Counts{'tmppreserved'}{$1}{$2}++; } elsif ($p1 =~ /^Open relay\? Nonlocal recips but not originating/) { $Totals{'warningsecurity'}++; $Counts{'warningsecurity'}{$p1}++ if ($Collecting{'warningsecurity'}); } # keep before general warnings below, so sadiag gets first crack at log # lines beginning with "(!) ...". elsif ($p1 =~ /^(?:\(!+\))?\!?SA (warn|info|error): (.*)$/) { #TD SA warn: FuzzyOcr: Cannot find executable for gocr my ($level,$msg) = ($1,$2); # XXX later, maybe break out stats on FuzzyOcr # skip "image too small" for now if ($msg =~ /^FuzzyOcr: Skipping .+, image too small$/) { #TD SA warn: FuzzyOcr: Skipping ocrad, image too small #TD SA warn: FuzzyOcr: Skipping ocrad-decolorize, image too small #$Counts{'sadiags'}{'fuzzyocr'}{'image too small'}++; next; } elsif ($msg =~ /dns: \[\.\.\.\]/) { #TD SA info: dns: [...] ;; ADDITIONAL SECTION (1 record) next; } # canonicalize some PIDs and IDs elsif ($msg =~ s/^pyzor: \[\d+\] error/pyzor: [] error/) { #TD SA info: pyzor: [11550] error: TERMINATED, signal 15 (000f) } elsif ($msg =~ /dns: no likely matching queries for id \d+/) { $msg =~ s/\d+//; } elsif ($msg =~ /dns: no callback for id \d+/) { $msg =~ s/\d+.*$/.../; } # report other SA warn's $Totals{'sadiags'}++; next unless ($Collecting{'sadiags'}); $Counts{'sadiags'}{ucfirst($level)}{$msg}++; } # catchall for most other warnings elsif (($p1 =~ /^\(!+\)/) or ($p1 =~ /^TROUBLE/) or ($p1 =~ /Can't (?:connect to UNIX|send to) socket/) or ($p1 =~ /: Empty result from /) or ($p1 =~ /: Error reading from socket: Connection reset by peer/) or ($p1 =~ /open\(.*\): Permission denied/) or ($p1 =~ /^_?WARN: /) or ($p1 =~ /Can't send SIG \d+ to process \[\d+\]: Operation not permitted/) or ($p1 =~ /(policy protocol: INVALID(?: AM\.PDP)? ATTRIBUTE LINE: .*)$/) or ($p1 =~ /(DKIM signature verification disabled, corresponding features not available. If not intentional.*)$/) ) { #TD (!)loading policy bank "AM.PDP-SOCK": unknown field "0" #TD (!!)policy_server FAILED: SQL quarantine code not enabled at (eval 37) line 306, line 4. #TD (!!)policy_server FAILED: Can't open file /var/spool/amavis/quarantine/spam-CFJYXmeS+FLy: Permission denied at (eval 37) line 330, line 5. #TD ClamAV-clamd: Empty result from /var/run/clamav/clamd, retrying (1) #TDdcc open(/var/dcc/map): Permission denied #TD TROUBLE in check_mail: FAILED: Died at /usr/sbin/amavisd-maia line 2872, line 22. #TD TROUBLE in check_mail: spam_scan FAILED: DBD::mysql::st execute failed: MySQL server has gone away at /usr/sbin/amavisd-maia line 3786, line 3036. #TD TROUBLE in process_request: DBD::mysql::st execute failed: MySQL server has gone away at (eval 35) line 258, line 3. #TD TROUBLE in process_request: DBD::mysql::st execute failed: Lost connection to MySQL server during query at (eval 35) line 258, line 3. #TD TROUBLE in process_request: Can't call method "disconnect" on an undefined value at /usr/sbin/amavisd-maia line 2895, line 22. #TD TROUBLE: recipient not done: smtp response ... #TD (!!)TROUBLE in process_request: Can't create file /var/amavis/tmp/amavis-98/email.txt: File exists at /usr/local/sbin/amavisd line 4774, line 4. #TD TROUBLE: lookup table is an unknown object: object ... #TD (!) policy protocol: INVALID ATTRIBUTE LINE: /var/spool/courier/tmp/114528/D967099\n #TD (!) policy protocol: INVALID AM.PDP ATTRIBUTE LINE: /var/spool/courier/tmp/114528/D967099\n #TD _WARN: bayes: cannot open bayes databases /var/spool/amavis/.spamassassin/bayes_* R/W: lock failed: Interrupted system call\n $p1 =~ s/^\(!+\)s*//; if ($p1 =~ /^WARN: (Using cpio instead of pax .*)$/) { #TD (!)WARN: Using cpio instead of pax can be a security risk; please add: $pax='pax'; to amavisd.conf and check that the pax(1) utility is available on the system! $Totals{'warningsecurity'}++; $Counts{'warningsecurity'}{$1}++ if ($Collecting{'warningsecurity'}); next; } $p1 =~ s/, retrying\s+\(\d+\)$//; strip_trace($p1); # canonicalize variations of the same message $p1 =~ s/^run_av \(([^,]+), built-in i\/f\)/$1/; $p1 =~ s/ av-scanner FAILED: CODE\(0x[^)]+\)/:/; $p1 =~ s/^(.+: Too many retries to talk to \S+) .*/$1/; if (($p1 =~ /(\S+): Can't (?:connect|send) to (?:UNIX )?(.*)$/) or ($p1 =~ /(\S+): (Too many retries to talk to .*)$/)) { #TD (!)ClamAV-clamd: Can't connect to UNIX socket /var/run/clamav/clamd.socket: No such file or directory, retrying (2) #TD (!)ClamAV-clamd: Can't connect to UNIX socket /var/run/clamav/clamd: Connection refused, retrying (2) #TD ClamAV-clamd: Can't connect to UNIX socket /var/run/clamav/clamd: Connection refused, retrying (1) #TD ClamAV-clamd: Can't send to socket /var/run/clamav/clamd: Transport endpoint is not connected, retrying (1) #TD Sophie: Can't send to socket /var/run/sophie: Transport endpoint is not connected, retrying (1) #TD (!)run_av (Sophie, built-in i/f): Too many retries to talk to /var/run/sophie (timed out) at (eval 55) line 310, line 16. #TD (!)run_av (ClamAV-clamd, built-in i/f): Too many retries to talk to /var/run/clamav/clamd.socket (Can't connect to UNIX socket /var/run/clamav/clamd.socket: No such file or directory) at (eval 52) line 310. #TD (!!)ClamAV-clamd av-scanner FAILED: CODE(0x804fa08) Too many retries to talk to /var/run/clamav/clamd.socket (Can't connect to UNIX socket /var/run/clamav/clamd.socket: No such file or directory) at (eval 52) line 310. at (eval 52) line 511. #TD (!!)Sophie av-scanner FAILED: CODE(0x814fd24) Too many retries to talk to /var/run/sophie (timed out) at (eval 55) line 310, line 16. at (eval 55) line 511, line 16. $Totals{'avconnectfailure'}++; $Counts{'avconnectfailure'}{$1}{ucfirst($2)}++ if ($Collecting{'avconnectfailure'}); next; } # simplify or canonicalize variations of the same message $p1 =~ s/^TROUBLE(:| in) //; $p1 =~ s/^_?WARN: //; $p1 =~ s/Can't create file \S+: (.+)$/Can't create file: $1/; $p1 =~ s/Can't send SIG \d+ to process \[\d+\]/Can't send SIG to process/; $Totals{'warning'}++; next unless ($Collecting{'warning'}); $Counts{'warning'}{$p1}++; } # Begin forced warnings: Keep this code below warning catchall elsif ($p1 =~ /^lookup_sql: /) { #TD lookup_sql: 2006, MySQL server has gone away $Totals{'warningsql'}++; next unless ($Collecting{'warningsql'}); $Counts{'warningsql'}{'SQL died'}++; } elsif (($reason,$item) = ($p1 =~ /^connect_to_sql: ([^']+) '\S+': (.*?)(?: \(\d+\))?$/) or ($item,$reason) = ($p1 =~ /^lookup_sql_field\((.*)\) \(WARN: (no such field in the SQL table)\)/)) { #TD connect_to_sql: unable to connect to DSN 'DBI:mysql:maia:sqlhost1.example.com': Lost connection to MySQL server during query #TD connect_to_sql: unable to connect to DSN 'DBI:mysql:maia:sqlhost2.example.com': Can't connect to MySQL server on 'sqlhost2.example.com' (111) #TD lookup_sql_field(id) (WARN: no such field in the SQL table), "from@example.com" result=undef $Totals{'warningsql'}++; next unless ($Collecting{'warningsql'}); $Counts{'warningsql'}{ucfirst("$reason: $item")}++; } # End forced warnings # panic elsif ( ($p2) = ($p1 =~ /^(?:\(!\)\s*)?PANIC, (.*)$/)) { #TD PANIC, PANIC, SA produced a clone process of [19122], TERMINATING CLONE [19123] $Totals{'panic'}++; next unless ($Collecting{'panic'}); $Counts{'panic'}{$p2}++; } # fatal elsif ( $p1 =~ /^Requesting process rundown after fatal error$/) { #TD Requesting process rundown after fatal error $Totals{'fatal'}++; next unless ($Collecting{'fatal'}); $Counts{'fatal'}{$p1}++; # DCC } elsif (($reason) = ($p1 =~ /^(missing message body; fatal error)/) or ($reason) = ($p1 =~ /^(try to start dccifd)/)) { $Totals{'dccerror'}++; next unless ($Collecting{'dccerror'}); $Counts{'dccerror'}{ucfirst($reason)}++; } elsif ($p1 =~ /^continue not asking DCC \d+ seconds after failure/) { $Totals{'dccerror'}++; next unless ($Collecting{'dccerror'}); $Counts{'dccerror'}{'Continue not asking DCC after failure'}++; } elsif ($p1 =~ /^no DCC answer from (\S+) after \d+ ms$/) { $Totals{'dccerror'}++; next unless ($Collecting{'dccerror'}); $Counts{'dccerror'}{"No answer from $1"}++; } elsif ( ($reason, $from, $to) = ($p1 =~ /^skip local delivery\((\d+)\): <(.*?)> -> <(.*?)>$/)) { $Totals{'localdeliveryskipped'}++; next unless ($Collecting{'localdeliveryskipped'}); $from = '<>' if ($from eq ''); $reason = $reason == 1 ? "No localpart" : $reason == 2 ? "Local alias is null" : "Other"; $Counts{'localdeliveryskipped'}{$reason}{$from}{$to}++; } # hard and soft whitelisted/blacklisted elsif ($p1 =~ /^wbl: (.*)$/) { # ignore wbl entries, can't think of good way to reliably summarize. # and 'black or whitelisted by all' makes using by-white or -black list # groupings impossible next; =cut $p1 = $1; # TD wbl: black or whitelisted by all recips next if ($p1 =~ /^black or whitelisted/); # not clear how to report this, so skip next if ($p1 =~ /^checking sender/); # ll 4 next if ($p1 =~ /^(LDAP) query keys/); # ll 5 next if ($p1 =~ /^(LDAP) recip/); # ll 5 next if ($p1 =~ /^recip <[^>]*> (?:black|white)listed sender/); # ll 5 # lookup order: SQL, LDAP, static if ($p1 =~ s/^\(SQL\) recip <[^>]*>//) { next if ($p1 =~ /^, \S+ matches$/); # ll 5 next if ($p1 =~ /^, rid=/); # ll 4 next if ($p1 =~ /^ is neutral to sender/); # ll 5 next if ($p1 =~ /^ (?:white|black)listed sender whitelisted sender <%s>, '. unexpected wb field value } #ll2 # wbl: (SQL) soft-(white|black)listed (%s) sender <%s> => <%s> (rid=%s)', $val, $sender, $recip, $user_id); # multiple senders: message sender, then "from", etc. #ll2 # wbl: soft-(white|black)listed (%s) sender <%s> => <%s>, #TD wbl: whitelisted sender #TD wbl: soft-whitelisted (-3) sender => , recip_key="." #TD wbl: whitelisted by user@example.com, but not by all, sender , # wbl: (whitelisted|blacklisted|black or whitelisted by all recips|(white|black)listed by xxx,yyy,... but not by all) sender %s if ($p1 =~ /^(?:\(SQL\) )?(?:(soft)-)?((?:white|black)listed)(?: \([^)]+\))? sender <([^>]*)>/) { my ($type,$list,$sender) = ($1,$2,$3); $Totals{$list}++; next unless ($Collecting{$list}); $type = $type ? 'Soft' : 'Hard' ; my ($localpart, $domainpart) = split (/@/, lc $sender); ($localpart, $domainpart) = ($sender, '*unspecified') if ($domainpart eq ''); $Counts{$list}{$type}{$domainpart}{$localpart}++; } else { inc_unmatched('wbl'); next; } =cut } # XXX: WHITELISTED or BLACKLISTED should be caught in SPAM tag above elsif (($p1 =~ /^white_black_list: whitelisted sender/) or ($p1 =~ /.* WHITELISTED/) ) { $Totals{'whitelisted'}++; } elsif (($p1 =~ /^white_black_list: blacklisted sender/) or ( $p1 =~ /.* BLACKLISTED/) ) { $Totals{'blacklisted'}++; } elsif ($p1 =~ /^Turning AV infection into a spam report: score=([^,]+), (.+)$/) { #TD Turning AV infection into a spam report: score=4.1, AV:Sanesecurity.ScamL.375.UNOFFICIAL=4.1 #TD Turning AV infection into a spam report: score=3.4, AV:Sanesecurity.Phishing.Cur.180.UNOFFICIAL=3.1,AV:Sanesecurity.Phishing.Cur.180.UNOFFICIAL=3.4 #BAT.Backdoor.Poisonivy.E178-SecuriteInfo.com next unless ($Collecting{'malwaretospam'}); #my $score_max = $1; my @list = split (/,/, $2); @list = unique_list(\@list); foreach (@list) { my ($name,$score) = split (/=/,$_); $name =~ s/^AV://; my $type = $name =~ s/\.UNOFFICIAL$// ? 'Unofficial' : 'Official'; # strip trailing numeric variant (...Phishing.Cur.863) my $variant = $name =~ s/([.-]\d+)$// ? $1 : '*invariant'; $Counts{'malwaretospam'}{$type}{$name}{$variant}{$score}++ } # The virus_scan line reports only the one virus name when more than one scanner detects a virus. # Use instead the ask_av and run_av lines (see below) # #} elsif ( my ($malware, $scanners) = ($p1 =~ /virus_scan: \(([^)]+)\), detected by \d+ scanners: (.*)$/ )) { #TD virus_scan: (HTML.Phishing.Bank-43), detected by 1 scanners: ClamAV-clamd #TD virus_scan: (Worm.SomeFool.D, Worm.SomeFool.D), detected by 1 scanners: ClamAV-clamd #TD virus_scan: (Trojan.Downloader.Small-9993), detected by 2 scanners: ClamAV-clamd, NAI McAfee AntiVirus (uvscan) # foreach (split /, /, $scanners) { # #$Totals{'malwarebyscanner'}++; # No summary output: redundant w/malwarepassed,malwareblocked} # $Counts{'malwarebyscanner'}{"$_"}{$malware}++; # } } elsif ($p1 =~ /^(?:ask_av|run_av) (.*)$/) { next unless ($Collecting{'malwarebyscanner'}); if (my ($scanner, $name) = ($1 =~ /^\((.+)\):(?: [^:]+)? INFECTED: ([^,]+)/)) { #TD ask_av (ClamAV-clamd): /var/amavis/tmp/amavis-20070830T070403-13776/parts INFECTED: Email.Malware.Sanesecurity.07082700 #TD run_av (NAI McAfee AntiVirus (uvscan)): INFECTED: W32/Zhelatin.gen!eml, W32/Zhelatin.gen!eml my $type = $name =~ s/\.UNOFFICIAL$// ? 'Unofficial' : 'Official'; my $variant = ''; if ($name =~ s/([.-]\d+)$//) { # strip trailing numeric variant (...Phishing.Cur.863) $variant = $1; } $Counts{'malwarebyscanner'}{$scanner}{$type}{$name}{$variant}++; } # currently ignoring other ask_av or run_av lines } # Extra Modules loaded at runtime #TD extra modules loaded after daemonizing/chrooting: Mail/SPF/Query.pm elsif (($item) = ( $p1 =~ /^extra modules loaded(?: after daemonizing(?:\/chrooting)?)?: (.+)$/)) { #TD extra modules loaded: PerlIO.pm, PerlIO/scalar.pm foreach my $code (split /, /, $item) { #TD extra modules loaded: unicore/lib/gc_sc/Digit.pl, unicore/lib/gc_sc/SpacePer.pl # avoid useless reporting of pseudo-modules which can't be pre-loaded once unless ($code =~ m#^unicore/lib/#) { $Totals{'extramodules'}++; $Counts{'extramodules'}{$code}++ if ($Collecting{'extramodules'}); } } # Timing report } elsif (my ($total,$report) = ( $p1 =~ /^(?:size: \d+, )?TIMING \[total (\d+) ms(?:, [^]]+)?\] - (.+)$/)) { next if ($report =~ /^got data/); # skip amavis release timing #TD TIMING [total 5808 ms] - SMTP greeting: 5 (0%)0, SMTP LHLO: 1 (0%)0, SMTP pre-MAIL: 2 (0%)0, SMTP pre-DATA-flush: 5 (0%)0, SMTP DATA: 34 (1%)1, check_init: 1 (0%)1 # older format, maia mailguard #TD TIMING [total 3795 ms] - SMTP EHLO: 1 (0%), SMTP pre-MAIL: 0 (0%), maia_read_system_config: 1 (0%), maia_get_mysql_size_limit: 0 (0%), SA check: 3556 (94%), rundown: 0 (0%) # v2.8.1 # .... size: 3815, TIMING [total 1901 ms, cpu 657 ms] - ... # Timing line is incomplete - let's report it if ($p1 !~ /\d+ \(\d+%\)\d+$/ and $p1 !~ /\d+ \(\d+%\)$/) { inc_unmatched('timing'); next; } if ($Opts{'timings'}) { my @pairs = split(/[,:] /, $report); while (my ($key,$value) = @pairs) { #4 (0%)0 my ($ms) = ($value =~ /^([\d.]+) /); # maintain a per-test list of timings push @{$Timings{$key}}, $ms; shift @pairs; shift @pairs; } push @TimingsTotals, $total; } } elsif ((($total,$report) = ( $p1 =~ /^TIMING-SA total (\d+) ms - (.+)$/ )) or (($total,$report) = ( $p1 =~ /^TIMING-SA \[total (\d+) ms, cpu \d+ ms\] - (.+)$/ ))) { #TIMING-SA [total 3219 ms, cpu 432 ms] - parse: 6 (0.2%), ext #TD TIMING-SA total 5478 ms - parse: 1.69 (0.0%), extract_message_metadata: 16 (0.3%), get_uri_detail_list: 2 (0.0%), tests_pri_-1000: 25 (0.4%), tests_pri_-950: 0.67 (0.0%), tests_pri_-900: 0.83 (0.0%), tests_pri_-400: 19 (0.3%), check_bayes: 17 (0.3%), tests_pri_0: 5323 (97.2%), check_spf: 12 (0.2%), poll_dns_idle: 0.81 (0.0%), check_dkim_signature: 1.50 (0.0%), check_razo r2: 5022 (91.7%), check_dcc: 192 (3.5%), check_pyzor: 0.02 (0.0%), tests_pri_500: 9 (0.2%), tests_pri_1000: 24 (0.4%), total_awl: 23 (0.4%), check_awl: 10 (0.2%), update_awl: 8 (0.1%), learn: 36 (0.7%), get_report: 1.77 (0.0%) # Timing line is incomplete - let's report it if ($p1 !~ /[\d.]+ \([\d.]+%\)[\d.]+$/ and $p1 !~ /[\d.]+ \([\d.]+%\)$/) { inc_unmatched('timing-sa'); next; } if ($Opts{'sa_timings'}) { my @pairs = split(/[,:] /, $report); while (my ($key,$value) = @pairs) { #4 (0%)0 my ($ms) = ($value =~ /^([\d.]+) /); # maintain a per-SA test list of timings push @{$TimingsSA{$key}}, $ms; shift @pairs; shift @pairs; } push @TimingsSATotals, $total; } # Bounce killer: 2.6+ } elsif ($p1 =~ /^bounce (.*)$/) { #TD bounce killed, -> , from: user@example.com, message-id: , return-path: #TD bounce rescued by domain, -> , from: user@example.com, message-id: , return-path: #TD bounce rescued by originating, -> , from: user@example.com, message-id: , return-path: #TD bounce rescued by: pen pals disabled, -> , from: user@example.com, message-id: , return-path: $p2 = $1; if ($p2 =~ /^killed, <(.+?)> -> /) { $Totals{'bouncekilled'}++; $Counts{'bouncekilled'}{$1 eq '' ? '<>' : $1}++ if ($Collecting{'bouncekilled'}); } elsif ($p2 =~ /^rescued by ([^,]+), <(.+?)> -> /) { # note: ignores "rescued by: pen pals disabled" $Totals{'bouncerescued'}++; $Counts{'bouncerescued'}{'By ' . $1}{$2 eq '' ? '<>' : $2}++ if ($Collecting{'bouncerescued'}); } elsif ($p2 =~ /^unverifiable, <(.+?)> -> /) { # note: ignores "rescued by: pen pals disabled" $Totals{'bounceunverifiable'}++; $Counts{'bounceunverifiable'}{$1 eq '' ? '<>' : $1}++ if ($Collecting{'bounceunverifiable'}); } #TD bounce unverifiable, -> #TD bounce unverifiable, <> -> } # Decoders elsif (my ($suffix, $info) = ( $p1 =~ /^Internal decoder for (\.\S*)\s*(?:\(([^)]*)\))?$/)) { #TD Internal decoder for .gz (backup, not used) #TD Internal decoder for .zip next unless ($Opts{'startinfo'}); $StartInfo{'Decoders'}{'Internal'}{$suffix} = $info; } elsif (($suffix, $decoder) = ( $p1 =~ /^No decoder for\s+(\.\S*)\s*(?:tried:\s+(.*))?$/)) { #TD No decoder for .tnef tried: tnef # older #TD No decoder for .doc next unless ($Opts{'startinfo'}); $StartInfo{'Decoders'}{'None'}{$suffix} = "tried: " . ($decoder ? $decoder : "unknown"); } elsif (($suffix, $decoder) = ( $p1 =~ /^Found decoder for\s+(\.\S*)\s+at\s+(.*)$/)) { #TD Found decoder for .bz2 at /usr/bin/bzip2 -d #TD Found decoder for .bz2 at /usr/bin/7za (backup, not used) next unless ($Opts{'startinfo'}); $StartInfo{'Decoders'}{'External'}{$suffix} = exists $StartInfo{'Decoders'}{'External'}{$suffix} ? join '; ', $StartInfo{'Decoders'}{'External'}{$suffix}, $decoder : $decoder; } # AV Scanners elsif (my ($tier, $scanner, $location) = ( $p1 =~ /^Found (primary|secondary) av scanner (.+) at (.+)$/)) { #TD Found primary av scanner NAI McAfee AntiVirus (uvscan) at /usr/local/bin/uvscan #TD Found secondary av scanner ClamAV-clamscan at /usr/local/bin/clamscan next unless ($Opts{'startinfo'}); $StartInfo{'AVScanner'}{"\u$tier"}{$scanner} = $location; } elsif (($tier, $scanner, $location) = ( $p1 =~ /^No (primary|secondary) av scanner: (.+)$/)) { #TD No primary av scanner: CyberSoft VFind next unless ($Opts{'startinfo'}); $StartInfo{'AVScanner'}{"\u$tier (not found)"}{$scanner} = ''; } elsif ( (($tier, $scanner) = ( $p1 =~ /^Using internal av scanner code for \(([^)]+)\) (.+)$/)) or (($tier, $scanner) = ( $p1 =~ /^Using (.*) internal av scanner code for (.+)$/))) { #TD Using internal av scanner code for (primary) ClamAV-clamd #TD Using primary internal av scanner code for ClamAV-clamd next unless ($Opts{'startinfo'}); $StartInfo{'AVScanner'}{"\u$tier internal"}{$scanner} = ''; # (Un)Loaded code, protocols, etc. } elsif (my ($code, $loaded) = ( $p1 =~ /^(\S+)\s+(?:proto? |base |protocol )?\s*(?:code)?\s+((?:NOT )?loaded)$/)) { next unless ($Opts{'startinfo'}); $StartInfo{'Code'}{"\u\L$loaded"}{$code} = ""; } elsif (my ($module, $vers) = ( $p1 =~ /^Module (\S+)\s+(.+)$/)) { #TD Module Amavis::Conf 2.086 next unless ($Opts{'startinfo'}); $StartInfo{'Code'}{'Loaded'}{$module} = $vers; } elsif (($module, my $families) = ( $p1 =~ /^socket module (\S+),\s+(.+)$/)) { #TD socket module IO::Socket::IP, protocol families available: INET, INET6 next unless ($Opts{'startinfo'}); $StartInfo{'Code'}{'Loaded'}{$module} = $families; } elsif (($code, $location) = ( $p1 =~ /^Found \$(\S+)\s+at\s+(.+)$/)) { #TD Found $file at /usr/bin/file #TD Found $uncompress at /usr/bin/gzip -d next unless ($Opts{'startinfo'}); $StartInfo{'Code'}{'Loaded'}{$code} = $location; } elsif (($code, $location) = ( $p1 =~ /^No \$(\S+),\s+not using it/)) { #TD No $dspam, not using it next unless ($Opts{'startinfo'}); $StartInfo{'Code'}{'Not loaded'}{$code} = $location; } elsif (($code, $location) = ( $p1 =~ /^No ext program for\s+([^,]+), (tried: .+)/)) { #TD No ext program for .kmz, tried: 7za, 7z #TD No ext program for .F, tried: unfreeze, freeze -d, melt, fcat next unless ($Opts{'startinfo'}); $StartInfo{'Code'}{'Not found'}{$code} = $location; } elsif ( $p1 =~ /^starting\.\s+(.+) at \S+ (?:amavisd-new-|Maia Mailguard )([^,]+),/) { #TD starting. /usr/local/sbin/amavisd at mailhost.example.com amavisd-new-2.5.0 (20070423), Unicode aware, LANG="C" #TD starting. /usr/sbin/amavisd-maia at vwsw02.eon.no Maia Mailguard 1.0.2, Unicode aware, LANG=en_US.UTF-8 next unless ($Opts{'startinfo'}); %StartInfo = () if !exists $StartInfo{'Logging'}; $StartInfo{'ampath'} = $1; $StartInfo{'amversion'} = $2; } elsif ( $p1 =~ /^config files read: (.*)$/) { #TD config files read: /etc/amavisd.conf, /etc/amavisd-overrides.conf next unless ($Opts{'startinfo'}); $StartInfo{'Configs'} = "$1"; } elsif ($p1 =~ /^Creating db in ([^;]+); [^,]+, (.*)$/) { #TD Creating db in /var/spool/amavis/db/; BerkeleyDB 0.31, libdb 4.4 next unless ($Opts{'startinfo'}); $StartInfo{'db'} = "$1\t($2)"; } elsif ($p1 =~ /^BerkeleyDB-based Amavis::Cache not available, using memory-based local cache$/) { #TD BerkeleyDB-based Amavis::Cache not available, using memory-based local cache next unless ($Opts{'startinfo'}); $StartInfo{'db'} = "BerkeleyDB\t(memory-based cache: Amavis::Cache unavailable)"; } elsif (my ($log) = ($p1 =~ /^logging initialized, log (level \d+, (?:STDERR|syslog: \S+))/)) { next unless ($Opts{'startinfo'}); %StartInfo = (); # first amavis log entry, clear out previous start info $StartInfo{'Logging'} = $log; } elsif (( $p1 =~ /^(:?perl=[^,]*, )?user=([^,]*), EUID: (\d+) [(](\d+)[)];\s+group=([^,]*), EGID: ([\d ]+)[(]([\d ]+)[)]/)) { # uninteresting... #next unless ($Opts{'startinfo'}); #$StartInfo{'IDs'}{'user'} = $1; #$StartInfo{'IDs'}{'euid'} = $2; #$StartInfo{'IDs'}{'uid'} = $3; #$StartInfo{'IDs'}{'group'} = $4; #$StartInfo{'IDs'}{'egid'} = $5; #$StartInfo{'IDs'}{'gid'} = $6; } elsif ($p1 =~ /^after_chroot_init: EUID: (\d+) [(](\d+)[)]; +EGID: ([\d ]+)[(]([\d ]+)[)]/) { #TD after_chroot_init: EUID: 999 (999); EGID: 54322 54322 54322 (54322 54322 54322) # uninteresting... } elsif ($p1 =~ /^SpamAssassin debug facilities: (.*)$/) { next unless ($Opts{'startinfo'}); $StartInfo{'sa_debug'} = $1; # amavis >= 2.6.3 } elsif ($p1 =~ /^SpamAssassin loaded plugins: (.*)$/) { #TD SpamAssassin loaded plugins: AWL, AutoLearnThreshold, Bayes, BodyEval, Check, DCC, DKIM, DNSEval, HTMLEval, HTTPSMismatch, Hashcash, HeaderEval, ImageInfo, MIMEEval, MIMEHeader, Pyzor, Razor2, RelayEval, ReplaceTags, SPF, SpamCop, URIDNSBL, URIDetail, URIEval, VBounce, WLBLEval, WhiteListSubject next unless ($Opts{'startinfo'}); map { $StartInfo{'SAPlugins'}{'Loaded'}{$_} = '' } split(/, /, $1); } elsif (($p2) = ( $p1 =~ /^Net::Server: (.*)$/ )) { next unless ($Opts{'startinfo'}); if ($p2 =~ /^.*starting! pid\((\d+)\)/) { #TD Net::Server: 2007/05/02-11:05:24 Amavis (type Net::Server::PreForkSimple) starting! pid(4405) $StartInfo{'Server'}{'pid'} = $1; } elsif ($p2 =~ /^Binding to UNIX socket file (.*) using/) { #TD Net::Server: Binding to UNIX socket file /var/spool/amavis/amavisd.sock using SOCK_STREAM $StartInfo{'Server'}{'socket'} = $1; } elsif ($p2 =~ /^Binding to TCP port (\d+) on host (.*)$/) { #TD Net::Server: Binding to TCP port 10024 on host 127.0.0.1 $StartInfo{'Server'}{'ip'} = "$2:$1"; } elsif ($p2 =~ /^Setting ([ug]id) to "([^"]+)"$/) { $StartInfo{'Server'}{$1} = $2; #TD Net::Server: Setting gid to "91 91" #TD Net::Server: Setting uid to "91" } # skip others } # higher debug level or rare messages skipped last elsif (! check_ignore_list ($p1, @ignore_list_final)) { inc_unmatched('final'); } } ######################################## # Final tabulations, and report printing # spamblocked includes spamdiscarded; adjust here $Totals{'spamblocked'} -= $Totals{'spamdiscarded'}; #Totals: Blocked/Passed totals $Totals{'totalblocked'} += $Totals{$_} foreach ( qw( malwareblocked bannednameblocked uncheckedblocked spamblocked spamdiscarded spammyblocked badheaderblocked oversizedblocked mtablocked cleanblocked tempfailblocked otherblocked )); $Totals{'totalpassed'} += $Totals{$_} foreach ( qw( malwarepassed bannednamepassed uncheckedpassed spampassed spammypassed badheaderpassed oversizedpassed mtapassed cleanpassed tempfailpassed otherpassed )); # Priority: VIRUS BANNED UNCHECKED SPAM SPAMMY BADH OVERSIZED MTA CLEAN #Totals: Ham/Spam $Totals{'totalmalware'} += $Totals{$_} foreach ( qw(malwarepassed malwareblocked)); $Totals{'totalbanned'} += $Totals{$_} foreach ( qw(bannednamepassed bannednameblocked)); $Totals{'totalunchecked'} += $Totals{$_} foreach ( qw(uncheckedpassed uncheckedblocked)); $Totals{'totalspammy'} += $Totals{$_} foreach ( qw(spammypassed spammyblocked)); $Totals{'totalbadheader'} += $Totals{$_} foreach ( qw(badheaderpassed badheaderblocked)); $Totals{'totaloversized'} += $Totals{$_} foreach ( qw(oversizedpassed oversizedblocked)); $Totals{'totalmta'} += $Totals{$_} foreach ( qw(mtapassed mtablocked)); $Totals{'totalclean'} += $Totals{$_} foreach ( qw(cleanpassed cleanblocked)); $Totals{'totalother'} += $Totals{$_} foreach ( qw(tempfailpassed tempfailblocked otherpassed otherblocked)); $Totals{'totalspam'} += $Totals{$_} foreach ( qw(spampassed spamblocked spamdiscarded totalspammy)); # everything lower priority than SPAMMY is considered HAM $Totals{'totalham'} += $Totals{$_} foreach ( qw(totalbadheader totaloversized totalmta totalclean)); $Totals{'totalmsgs'} += $Totals{$_} foreach ( qw(totalmalware totalbanned totalunchecked totalspam totalham totalother)); # Print the summary report if any key has non-zero data. # Note: must explicitly check for any non-zero data, # as Totals always has some keys extant. # if ($Opts{'summary'}) { for (keys %Totals) { if ($Totals{$_}) { print_summary_report (@Sections); last; } } } # Print the detailed report, if detail is sufficiently high # if ($Opts{'detail'} >= 5) { print_detail_report (@Sections); printAutolearnReport; printSpamScorePercentilesReport; printSpamScoreFrequencyReport; printSARulesReport; printTimingsReport("Scan Timing Percentiles", \%Timings, \@TimingsTotals, $Opts{'timings'}); printTimingsReport("SA Timing Percentiles", \%TimingsSA, \@TimingsSATotals, 0-$Opts{'sa_timings'}); printStartupInfoReport if ($Opts{'detail'} >= 10); } #{ #use Data::Dumper; #print Dumper(\%p0ftags); #print Dumper($Counts{'p0f'}); #} # Finally, print any unmatched lines # print_unmatched_report(); # Evaluates a given line against the list of ignore patterns. # sub check_ignore_list($ \@) { my ($line, $listref) = @_; foreach (@$listref) { return 1 if $line =~ /$_/; } return 0; } # Spam score percentiles report # =pod ================================================================================== Spam Score Percentiles 0% 50% 90% 95% 98% 100% ---------------------------------------------------------------------------------- Score Spam (100) 6.650 21.906 34.225 36.664 38.196 42.218 Score Ham (1276) -17.979 -2.599 0.428 2.261 3.472 6.298 ================================================================================== =cut sub printSpamScorePercentilesReport { return unless ($Opts{'score_percentiles'} and keys %SpamScores); #printf "Scores $_ (%d): @{$SpamScores{$_}}\n", scalar @{$SpamScores{$_}} foreach keys %SpamScores; my (@p, @sorted); my @percents = split /[\s,]+/, $Opts{'score_percentiles'}; my $myfw2 = $fw2 - 1; print "\n", $sep1 x $fw1, $sep1 x $fw2 x @percents; printf "\n%-${fw1}s" . "%${myfw2}s%%" x @percents , "Spam Score Percentiles", @percents; print "\n", $sep2 x $fw1, $sep2 x $fw2 x @percents; foreach my $ccat (keys %SpamScores) { @sorted = sort { $a <=> $b } @{$SpamScores{$ccat}}; @p = get_percentiles (@sorted, @percents); printf "\n%-${fw1}s" . "%${fw2}.3f" x scalar (@p), "Score \u$ccat (" . scalar (@sorted) . ')', @p; } print "\n", $sep1 x $fw1, $sep1 x $fw2 x @percents, "\n"; } # Spam score frequency report # =pod ====================================================================================================== Spam Score Frequency <= -10 <= -5 <= 0 <= 5 <= 10 <= 20 <= 30 > 30 ------------------------------------------------------------------------------------------------------ Hits (1376) 29 168 921 170 29 33 1 25 Percent of Hits 2.11% 12.21% 66.93% 12.35% 2.11% 2.40% 0.07% 1.82% ====================================================================================================== =cut sub printSpamScoreFrequencyReport { return unless ($Opts{'score_frequencies'} and keys %SpamScores); my @scores = (); push @scores, @{$SpamScores{$_}} foreach (keys %SpamScores); my $nscores = scalar @scores; my @sorted = sort { $a <=> $b } @scores; my @buckets = sort { $a <=> $b } split /[\s,]+/, $Opts{'score_frequencies'}; push @buckets, $buckets[-1] + 1; #print "Scores: @sorted\n"; my @p = get_frequencies (@sorted, @buckets); my @ranges = ( 0 ) x @buckets; my $last = @buckets - 1; $ranges[0] = sprintf "%${fw2}s", " <= $buckets[0]"; $ranges[-1] = sprintf "%${fw2}s", " > $buckets[-2]"; for my $i (1 .. @buckets - 2) { $ranges[$i] = sprintf "%${fw2}s", " <= $buckets[$i]"; } print "\n", $sep1 x $fw1, $sep1 x $fw2 x @buckets; printf "\n%-${fw1}s" . "%-${fw2}s" x @buckets , "Spam Score Frequency", @ranges; print "\n", $sep2 x $fw1, $sep2 x $fw2 x @buckets; printf "\n%-${fw1}s" . "%${fw2}d" x scalar (@p), "Hits ($nscores)", @p; my $myfw2 = $fw2 - 1; printf "\n%-${fw1}s" . "%${myfw2}.2f%%" x scalar (@p), "Percent of Hits", map {($_ / $nscores) * 100.0; } @p; print "\n", $sep1 x $fw1, $sep1 x $fw2 x @buckets, "\n"; } # SpamAssassin rules report # =pod =========================================================================== SpamAssassin Rule Hits: Spam --------------------------------------------------------------------------- Rank Hits % Msgs % Spam % Ham Score Rule ---- ---- ------ ------ ----- ----- ---- 1 44 81.48% 93.62% 0.00% 1.961 URIBL_BLACK 2 44 81.48% 93.62% 14.29% 0.001 HTML_MESSAGE 3 42 77.78% 89.36% 0.00% 2.857 URIBL_JP_SURBL 4 38 70.37% 80.85% 14.29% 2.896 RCVD_IN_XBL 5 37 68.52% 78.72% 0.00% 2.188 RCVD_IN_BL_SPAMCOP_NET ... =========================================================================== =========================================================================== SpamAssassin Rule Hits: Ham --------------------------------------------------------------------------- Rank Hits % Msgs % Spam % Ham Score Rule ---- ---- ------ ------ ----- ----- ---- 1 5 9.26% 2.13% 71.43% 0.001 STOX_REPLY_TYPE 2 4 7.41% 0.00% 57.14% -0.001 SPF_PASS 3 4 7.41% 6.38% 57.14% - AWL 4 1 1.85% 0.00% 14.29% 0.303 TVD_RCVD_SINGLE 5 1 1.85% 25.53% 14.29% 0.1 RDNS_DYNAMIC ... =========================================================================== =cut sub printSARulesReport { return unless (keys %{$Counts{'sarules'}}); our $maxlen = 0; sub getSAHitsReport($ $) { my ($type, $topn) = @_; my $i = 1; my @report = (); return if ($topn eq '0'); # topn can be numeric, or the string "all" for (sort { $Counts{'sarules'}{$type}{$b} <=> $Counts{'sarules'}{$type}{$a} } keys %{$Counts{'sarules'}{$type}}) { # only show top n lines; all when topn is "all" if ($topn ne 'all' and $i > $topn) { push @report, "...\n"; last; } my $n = $Counts{'sarules'}{$type}{$_}; my $nham = $Counts{'sarules'}{'Ham'}{$_}; my $nspam = $Counts{'sarules'}{'Spam'}{$_}; # rank, count, % msgs, % spam, % ham push @report, sprintf "%4d %8d %6.2f%% %6.2f%% %6.2f%% %s\n", $i++, $n, $Totals{'totalmsgs'} == 0 ? 0 : 100.0 * $n / $Totals{'totalmsgs'}, $Totals{'totalspam'} == 0 ? 0 : 100.0 * $nspam / $Totals{'totalspam'}, $Totals{'totalham'} == 0 ? 0 : 100.0 * $nham / $Totals{'totalham'}, $_; my $len = length($report[-1]) - 1; $maxlen = $len if ($len > $maxlen); } if (scalar @report) { print "\n", $sep1 x $maxlen, "\n"; print "SpamAssassin Rule Hits: $type\n"; print $sep2 x $maxlen, "\n"; print "Rank Hits % Msgs % Spam % Ham Score Rule\n"; print "---- ---- ------ ------ ----- ----- ----\n"; print @report; print $sep1 x $maxlen, "\n"; } } my ($def_limit_spam, $def_limit_ham) = split /[\s,]+/, $Defaults{'sarules'}; my ($limit_spam, $limit_ham) = split /[\s,]+/, $Opts{'sarules'}; $limit_spam = $def_limit_spam if $limit_spam eq ''; $limit_ham = $def_limit_ham if $limit_ham eq ''; getSAHitsReport('Spam', $limit_spam); getSAHitsReport('Ham', $limit_ham); } # Autolearn report, only available if enabled in amavis $log_templ template # =pod ====================================================================== Autolearn Msgs Spam Ham % Msgs % Spam % Ham ---------------------------------------------------------------------- Spam 36 36 0 66.67% 76.60% 0.00% Ham 2 0 2 3.70% 0.00% 28.57% No 7 4 3 12.96% 8.51% 42.86% Disabled 6 6 0 11.11% 12.77% 0.00% Failed 2 1 1 3.70% 2.13% 14.29% ---------------------------------------------------------------------- Totals 53 47 6 98.15% 100.00% 85.71% ====================================================================== =cut sub printAutolearnReport { #print "printAutolearnReport:\n" if ($Opts{'debug'}); return unless (keys %{$Counts{'autolearn'}}); our $maxlen = 0; our ($nhamtotal, $nspamtotal); sub getAutolearnReport($) { my ($type) = @_; my @report = (); # SA 2.5/2.6 : ham/spam/no # SA 3.0+ : ham/spam/no/disabled/failed/unavailable for (qw(spam ham no disabled failed unavailable)) { next unless (exists $Counts{'autolearn'}{'Spam'}{$_} or exists $Counts{'autolearn'}{'Ham'}{$_}); #print "printAutolearnReport: type: $_\n" if ($Opts{'debug'}); my $nham = exists $Counts{'autolearn'}{'Ham'}{$_} ? $Counts{'autolearn'}{'Ham'}{$_} : 0; my $nspam = exists $Counts{'autolearn'}{'Spam'}{$_} ? $Counts{'autolearn'}{'Spam'}{$_} : 0; my $nboth = $nham + $nspam; $nhamtotal += $nham; $nspamtotal += $nspam; # type, nspam, nham, % msgs, % spam, % ham push @report, sprintf "%-13s %9d %9d %9d %6.2f%% %6.2f%% %6.2f%%\n", ucfirst $_, $nspam + $nham, $nspam, $nham, $Totals{'totalmsgs'} == 0 ? 0 : 100.0 * $nboth / $Totals{'totalmsgs'}, $Totals{'totalspam'} == 0 ? 0 : 100.0 * $nspam / $Totals{'totalspam'}, $Totals{'totalham'} == 0 ? 0 : 100.0 * $nham / $Totals{'totalham'}; my $len = length($report[-1]) - 1; $maxlen = $len if ($len > $maxlen); } return @report; } my @report_spam = getAutolearnReport('Spam'); if (scalar @report_spam) { print "\n", $sep1 x $maxlen, "\n"; print "Autolearn Msgs Spam Ham % Msgs % Spam % Ham\n"; print $sep2 x $maxlen, "\n"; print @report_spam; print $sep2 x $maxlen, "\n"; printf "%-13s %9d %9d %9d %6.2f%% %6.2f%% %6.2f%%\n", 'Totals', $nspamtotal + $nhamtotal, $nspamtotal, $nhamtotal, $Totals{'totalmsgs'} == 0 ? 0 : 100.0 * ($nspamtotal + $nhamtotal) / $Totals{'totalmsgs'}, $Totals{'totalspam'} == 0 ? 0 : 100.0 * $nspamtotal / $Totals{'totalspam'}, $Totals{'totalham'} == 0 ? 0 : 100.0 * $nhamtotal / $Totals{'totalham'}; print $sep1 x $maxlen, "\n"; } } # Timings percentiles report, used for amavis message scanning and spamassassin timings =pod ======================================================================================================================== Scan Timing Percentiles % Time Total (ms) 0% 5% 25% 50% 75% 95% 100% ------------------------------------------------------------------------------------------------------------------------ AV-scan-2 (3) 69.23% 7209.00 2392.00 2393.50 2399.50 2407.00 2408.50 2409.70 2410.00 SA check (2) 19.74% 2056.00 942.00 950.60 985.00 1028.00 1071.00 1105.40 1114.00 SMTP DATA (3) 5.49% 572.00 189.00 189.20 190.00 191.00 191.50 191.90 192.00 AV-scan-1 (3) 0.82% 85.00 11.00 12.60 19.00 27.00 37.00 45.00 47.00 ... ------------------------------------------------------------------------------------------------------------------------ Total 10413.00 2771.00 2867.10 3251.50 3732.00 3821.00 3892.20 3910.00 ======================================================================================================================== ======================================================================================================================== SA Timing Percentiles % Time Total (ms) 0% 5% 25% 50% 75% 95% 100% ------------------------------------------------------------------------------------------------------------------------ tests_pri_0 (1) 97.17% 5323.00 5323.00 5323.00 5323.00 5323.00 5323.00 5323.00 5323.00 check_razor2 (1) 91.68% 5022.00 5022.00 5022.00 5022.00 5022.00 5022.00 5022.00 5022.00 check_dcc (1) 3.50% 192.00 192.00 192.00 192.00 192.00 192.00 192.00 192.00 learn (1) 0.66% 36.00 36.00 36.00 36.00 36.00 36.00 36.00 36.00 tests_pri_-1000 (1) 0.46% 25.00 25.00 25.00 25.00 25.00 25.00 25.00 25.00 ... ------------------------------------------------------------------------------------------------------------------------ Total 5478.00 5478.00 5478.00 5478.00 5478.00 5478.00 5478.00 5478.00 ======================================================================================================================== =cut sub printTimingsReport($$$$) { my ($title, $timingsref, $totalsref, $cutoff) = @_; my @tkeys = keys %$timingsref; return unless scalar @tkeys; my (@p, @sorted, %perkey_totals, @col_subtotals); my ($pcnt,$max_pcnt,$max_rows,$time_total_actual,$time_total_hypo,$subtotal_pcnt); my @percents = split /[\s,]+/, $Opts{'timings_percentiles'}; my $header_footer = $sep1 x 50 . ($sep1 x 10) x @percents; my $header_end = $sep2 x 50 . ($sep2 x 10) x @percents; my $title_width = '-28'; print "\n$header_footer\n"; printf "%${title_width}s %6s %13s" ." %8s%%" x @percents , $title, "% Time", "Total (ms)", @percents; print "\n$header_end\n"; # Sum the total time for each timing key foreach my $key (@tkeys) { foreach my $timeval (@{$$timingsref{$key}}) { $perkey_totals{$key} += $timeval; } } # Sum total time spent scanning map {$time_total_actual += $_} @$totalsref; # cutoff value used to limit the number of rows of output # positive cutoff is a percentage of cumulative time # negative cutoff limits number of rows if ($cutoff >= 0) { $max_pcnt = $cutoff != 100 ? $cutoff : 150; # 150% avoids roundoff errors } else { $max_rows = -$cutoff; } my $rows = 0; # sort each timing key's values, required to compute the list of percentiles for (sort { $perkey_totals{$b} <=> $perkey_totals{$a} } @tkeys) { last if (($max_rows and $rows >= $max_rows) or ($max_pcnt and $subtotal_pcnt >= $max_pcnt)); $pcnt = ($perkey_totals{$_} / $time_total_actual) * 100, @sorted = sort { $a <=> $b } @{$$timingsref{$_}}; @p = get_percentiles (@sorted, @percents); $subtotal_pcnt += $pcnt; printf "%${title_width}s %6.2f%% %13.2f" . " %9.2f" x scalar (@p) . "\n", $_ . ' (' . scalar(@{$$timingsref{$_}}) . ')', # key ( number of elements ) $pcnt, # percent of total time #$perkey_totals{$_} / 1000, # total time for this test $perkey_totals{$_}, # total time for this test #map {$_ / 1000} @p; # list of percentiles @p; # list of percentiles $rows++; } print "...\n" if ($rows != scalar @tkeys); print "$header_end\n"; # actual total time as reported by amavis @sorted = sort { $a <=> $b } @$totalsref; @p = get_percentiles (@sorted, @percents); printf "%${title_width}s %13.2f" . " %9.2f" x scalar (@p) . "\n", 'Total', #$time_total_actual / 1000, $time_total_actual, #map {$_ / 1000} @p; @p; print "$header_footer\n"; } # Most recent startup info report # sub printStartupInfoReport { return unless (keys %StartInfo); sub print2col($ $) { my ($label,$val) = @_; printf "%-50s %s\n", $label, $val; } print "\nAmavis Startup\n"; print2col (" Amavis", $StartInfo{'ampath'}) if (exists $StartInfo{'ampath'}); print2col (" Version", $StartInfo{'amversion'}) if (exists $StartInfo{'amversion'}); print2col (" PID", $StartInfo{'Server'}{'pid'}) if (exists $StartInfo{'Server'}{'pid'}); print2col (" Socket", $StartInfo{'Server'}{'socket'}) if (exists $StartInfo{'Server'}{'socket'}); print2col (" TCP port", $StartInfo{'Server'}{'ip'}) if (exists $StartInfo{'Server'}{'ip'}); print2col (" UID", $StartInfo{'Server'}{'uid'}) if (exists $StartInfo{'Server'}{'uid'}); print2col (" GID", $StartInfo{'Server'}{'gid'}) if (exists $StartInfo{'Server'}{'gid'}); print2col (" Logging", $StartInfo{'Logging'}) if (exists $StartInfo{'Logging'}); print2col (" Configuration Files", $StartInfo{'Configs'}) if (exists $StartInfo{'Configs'}); print2col (" SpamAssassin", $StartInfo{'sa_version'}) if (exists $StartInfo{'sa_version'}); print2col (" SpamAssassin Debug Facilities", $StartInfo{'sa_debug'}) if (exists $StartInfo{'sa_debug'}); print2col (" Database", $StartInfo{'db'}) if (exists $StartInfo{'db'}); #if (keys %{$StartInfo{'IDs'}}) { # print " Process startup user/group:\n"; # print " User: $StartInfo{'IDs'}{'user'}, EUID: $StartInfo{'IDs'}{'euid'}, UID: $StartInfo{'IDs'}{'uid'}\n"; # print " Group: $StartInfo{'IDs'}{'group'}, EGID: $StartInfo{'IDs'}{'egid'}, GID: $StartInfo{'IDs'}{'gid'}\n"; #} sub print_modules ($ $) { my ($key, $label) = @_; print " $label\n"; foreach (sort keys %{$StartInfo{$key}}) { print " $_\n"; foreach my $module (sort keys %{$StartInfo{$key}{$_}}) { if ($StartInfo{$key}{$_}{$module}) { print2col (" " . $module, $StartInfo{$key}{$_}{$module}); } else { print2col (" " . $module, ""); } } } }; print_modules('AVScanner', 'Antivirus scanners'); print_modules('Code', 'Code, modules and external programs'); print_modules('Decoders', 'Decoders'); print_modules('SAPlugins', 'SpamAssassin plugins'); } # Initialize the Getopts option list. Requires the Section table to # be built already. # sub init_getopts_table() { print "init_getopts_table: enter\n" if $Opts{'debug'} & D_ARGS; init_getopts_table_common(@supplemental_reports); add_option ('first_recip_only!'); add_option ('show_first_recip_only=i', sub { $Opts{'first_recip_only'} = $_[1]; 1;}); add_option ('startinfo!'); add_option ('show_startinfo=i', sub { $Opts{'startinfo'} = $_[1]; 1; }); add_option ('by_ccat_summary!'); add_option ('show_by_ccat_summary=i', sub { $Opts{'by_ccat_summary'} = $_[1]; 1; }); add_option ('noscore_percentiles', \&triway_opts); add_option ('score_percentiles=s', \&triway_opts); add_option ('noscore_frequencies', \&triway_opts); add_option ('score_frequencies=s', \&triway_opts); add_option ('nosa_timings', sub { $Opts{'sa_timings'} = 0; 1; }); add_option ('sa_timings=i'); add_option ('sa_timings_percentiles=s'); add_option ('notimings', sub { $Opts{'timings'} = 0; 1; }); add_option ('timings=i'); add_option ('timings_percentiles=s'); add_option ('nosarules', \&triway_opts); add_option ('sarules=s', \&triway_opts); #add_option ('nop0f', \&triway_opts); #add_option ('p0f=s', \&triway_opts); add_option ('autolearn!'); add_option ('show_autolearn=i', sub { $Opts{'autolearn'} = $_[1]; 1; }); } # Builds the entire @Section table used for data collection # # Each Section entry has as many as six fields: # # 1. Section array reference # 2. Key to %Counts, %Totals accumulator hashes, and %Collecting hash # 3. Output in Detail report? (must also a %Counts accumulator) # 4. Numeric output format specifier for Summary report # 5. Section title for Summary and Detail reports # 6. A hash to a divisor used to calculate the percentage of a total for that key # # Use begin_section_group/end_section_group to create groupings around sections. # # Sections can be freely reordered if desired, but maintain proper group nesting. # sub build_sect_table() { print "build_sect_table: enter\n" if $Opts{'debug'} & D_SECT; my $S = \@Sections; # References to these are used in the Sections table below; we'll predeclare them. $Totals{'totalmsgs'} = 0; # Place configuration and critical errors first # SECTIONREF, NAME, DETAIL, FMT, TITLE, DIVISOR begin_section_group ($S, 'warnings'); add_section ($S, 'fatal', 1, 'd', '*Fatal'); add_section ($S, 'panic', 1, 'd', '*Panic'); add_section ($S, 'warningsecurity', 1, 'd', '*Warning: Security risk'); add_section ($S, 'avtimeout', 1, 'd', '*Warning: Virus scanner timeout'); add_section ($S, 'avconnectfailure', 1, 'd', '*Warning: Virus scanner connection failure'); add_section ($S, 'warningsmtpshutdown', 1, 'd', '*Warning: SMTP shutdown'); add_section ($S, 'warningsql', 1, 'd', '*Warning: SQL problem'); add_section ($S, 'warningaddressmodified', 1, 'd', '*Warning: Email address modified'); add_section ($S, 'warningnoquarantineid', 1, 'd', '*Warning: Message missing X-Quarantine-ID header'); add_section ($S, 'warning', 1, 'd', 'Miscellaneous warnings'); end_section_group ($S, 'warnings'); begin_section_group ($S, 'scanned', "\n"); add_section ($S, 'totalmsgs', 0, 'd', [ 'Total messages scanned', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'bytesscanned', 0, 'Z', 'Total bytes scanned'); # Z means print scaled as in 1k, 1m, etc. end_section_group ($S, 'scanned', $sep1); # Blocked / Passed # Priority: VIRUS BANNED UNCHECKED SPAM SPAMMY BADH OVERSIZED MTA CLEAN begin_section_group ($S, 'passblock', "\n"); begin_section_group ($S, 'blocked', "\n"); add_section ($S, 'totalblocked', 0, 'd', [ 'Blocked', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'malwareblocked', 1, 'd', ' Malware blocked', \$Totals{'totalmsgs'}); add_section ($S, 'bannednameblocked', 1, 'd', ' Banned name blocked', \$Totals{'totalmsgs'}); add_section ($S, 'uncheckedblocked', 1, 'd', ' Unchecked blocked', \$Totals{'totalmsgs'}); add_section ($S, 'spamblocked', 1, 'd', ' Spam blocked', \$Totals{'totalmsgs'}); add_section ($S, 'spamdiscarded', 0, 'd', ' Spam discarded (no quarantine)', \$Totals{'totalmsgs'}); add_section ($S, 'spammyblocked', 1, 'd', ' Spammy blocked', \$Totals{'totalmsgs'}); add_section ($S, 'badheaderblocked', 1, 'd', ' Bad header blocked', \$Totals{'totalmsgs'}); add_section ($S, 'oversizedblocked', 1, 'd', ' Oversized blocked', \$Totals{'totalmsgs'}); add_section ($S, 'mtablocked', 1, 'd', ' MTA blocked', \$Totals{'totalmsgs'}); add_section ($S, 'cleanblocked', 1, 'd', ' Clean blocked', \$Totals{'totalmsgs'}); add_section ($S, 'tempfailblocked', 1, 'd', ' Tempfail blocked', \$Totals{'totalmsgs'}); add_section ($S, 'otherblocked', 1, 'd', ' Other blocked', \$Totals{'totalmsgs'}); end_section_group ($S, 'blocked'); begin_section_group ($S, 'passed', "\n"); add_section ($S, 'totalpassed', 0, 'd', [ 'Passed', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'malwarepassed', 1, 'd', ' Malware passed', \$Totals{'totalmsgs'}); add_section ($S, 'bannednamepassed', 1, 'd', ' Banned name passed', \$Totals{'totalmsgs'}); add_section ($S, 'uncheckedpassed', 1, 'd', ' Unchecked passed', \$Totals{'totalmsgs'}); add_section ($S, 'spampassed', 1, 'd', ' Spam passed', \$Totals{'totalmsgs'}); add_section ($S, 'spammypassed', 1, 'd', ' Spammy passed', \$Totals{'totalmsgs'}); add_section ($S, 'badheaderpassed', 1, 'd', ' Bad header passed', \$Totals{'totalmsgs'}); add_section ($S, 'oversizedpassed', 1, 'd', ' Oversized passed', \$Totals{'totalmsgs'}); add_section ($S, 'mtapassed', 1, 'd', ' MTA passed', \$Totals{'totalmsgs'}); add_section ($S, 'cleanpassed', 1, 'd', ' Clean passed', \$Totals{'totalmsgs'}); add_section ($S, 'tempfailpassed', 1, 'd', ' Tempfail passed', \$Totals{'totalmsgs'}); add_section ($S, 'otherpassed', 1, 'd', ' Other passed', \$Totals{'totalmsgs'}); end_section_group ($S, 'passed'); end_section_group ($S, 'passblock', $sep1); if ($Opts{'by_ccat_summary'}) { # begin level 1 group begin_section_group ($S, 'by_ccat', "\n"); # begin level 2 groupings begin_section_group ($S, 'malware', "\n"); # level 2 add_section ($S, 'totalmalware', 0, 'd', [ 'Malware', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'malwarepassed', 0, 'd', ' Malware passed', \$Totals{'totalmsgs'}); add_section ($S, 'malwareblocked', 0, 'd', ' Malware blocked', \$Totals{'totalmsgs'}); end_section_group ($S, 'malware'); begin_section_group ($S, 'banned', "\n"); add_section ($S, 'totalbanned', 0, 'd', [ 'Banned', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'bannednamepassed', 0, 'd', ' Banned file passed', \$Totals{'totalmsgs'}); add_section ($S, 'bannednameblocked', 0, 'd', ' Banned file blocked', \$Totals{'totalmsgs'}); end_section_group ($S, 'banned'); begin_section_group ($S, 'unchecked', "\n"); add_section ($S, 'totalunchecked', 0, 'd', [ 'Unchecked', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'uncheckedpassed', 0, 'd', ' Unchecked passed', \$Totals{'totalmsgs'}); add_section ($S, 'uncheckedblocked', 0, 'd', ' Unchecked blocked', \$Totals{'totalmsgs'}); end_section_group ($S, 'unchecked'); begin_section_group ($S, 'spam', "\n"); add_section ($S, 'totalspam', 0, 'd', [ 'Spam', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'spammypassed', 0, 'd', ' Spammy passed', \$Totals{'totalmsgs'}); add_section ($S, 'spammyblocked', 0, 'd', ' Spammy blocked', \$Totals{'totalmsgs'}); add_section ($S, 'spampassed', 0, 'd', ' Spam passed', \$Totals{'totalmsgs'}); add_section ($S, 'spamblocked', 0, 'd', ' Spam blocked', \$Totals{'totalmsgs'}); add_section ($S, 'spamdiscarded', 0, 'd', ' Spam discarded (no quarantine)', \$Totals{'totalmsgs'}); end_section_group ($S, 'spam'); begin_section_group ($S, 'ham', "\n"); add_section ($S, 'totalham', 0, 'd', [ 'Ham', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'badheaderpassed', 0, 'd', ' Bad header passed', \$Totals{'totalmsgs'}); add_section ($S, 'badheaderblocked', 0, 'd', ' Bad header blocked', \$Totals{'totalmsgs'}); add_section ($S, 'oversizedpassed', 0, 'd', ' Oversized passed', \$Totals{'totalmsgs'}); add_section ($S, 'oversizedblocked', 0, 'd', ' Oversized blocked', \$Totals{'totalmsgs'}); add_section ($S, 'mtapassed', 0, 'd', ' MTA passed', \$Totals{'totalmsgs'}); add_section ($S, 'mtablocked', 0, 'd', ' MTA blocked', \$Totals{'totalmsgs'}); add_section ($S, 'cleanpassed', 0, 'd', ' Clean passed', \$Totals{'totalmsgs'}); add_section ($S, 'cleanblocked', 0, 'd', ' Clean blocked', \$Totals{'totalmsgs'}); end_section_group ($S, 'ham'); begin_section_group ($S, 'other', "\n"); add_section ($S, 'totalother', 0, 'd', [ 'Other', '-' ], \$Totals{'totalmsgs'}); add_section ($S, 'tempfailpassed', 0, 'd', ' Tempfail passed', \$Totals{'totalmsgs'}); add_section ($S, 'tempfailblocked', 0, 'd', ' Tempfail blocked', \$Totals{'totalmsgs'}); add_section ($S, 'otherpassed', 0, 'd', ' Other passed', \$Totals{'totalmsgs'}); add_section ($S, 'otherblocked', 0, 'd', ' Other blocked', \$Totals{'totalmsgs'}); end_section_group ($S, 'other'); # end level 2 groupings # end level 1 group end_section_group ($S, 'by_ccat', $sep1); } begin_section_group ($S, 'misc', "\n"); add_section ($S, 'virusscanskipped', 1, 'd', 'Virus scan skipped'); add_section ($S, 'sabypassed', 0, 'd', 'SpamAssassin bypassed'); add_section ($S, 'satimeout', 0, 'd', 'SpamAssassin timeout'); add_section ($S, 'released', 1, 'd', 'Released from quarantine'); add_section ($S, 'defanged', 1, 'd', 'Defanged'); add_section ($S, 'truncatedheader', 0, 'd', 'Truncated headers > 998 characters'); add_section ($S, 'truncatedmsg', 0, 'd', 'Truncated message passed to SpamAssassin'); add_section ($S, 'tagged', 0, 'd', 'Spam tagged'); add_section ($S, 'smtpresponse', 1, 'd', 'SMTP response'); add_section ($S, 'badaddress', 1, 'd', 'Bad address syntax'); add_section ($S, 'fakesender', 1, 'd', 'Fake sender'); add_section ($S, 'archiveextract', 1, 'd', 'Archive extraction problem'); add_section ($S, 'dsnsuppressed', 1, 'd', 'DSN suppressed'); add_section ($S, 'dsnnotification', 1, 'd', 'DSN notification (debug supplemental)'); add_section ($S, 'bouncekilled', 1, 'd', 'Bounce killed'); add_section ($S, 'bouncerescued', 1, 'd', 'Bounce rescued'); add_section ($S, 'bounceunverifiable', 1, 'd', 'Bounce unverifiable'); add_section ($S, 'nosubject', 0, 'd', 'Subject header inserted'); add_section ($S, 'whitelisted', 1, 'd', 'Whitelisted'); add_section ($S, 'blacklisted', 1, 'd', 'Blacklisted'); add_section ($S, 'penpalsaved', 1, 'd', 'Penpals saved from kill'); add_section ($S, 'tmppreserved', 1, 'd', 'Preserved temporary directory'); add_section ($S, 'dccerror', 1, 'd', 'DCC error'); add_section ($S, 'mimeerror', 1, 'd', 'MIME error'); add_section ($S, 'defangerror', 1, 'd', 'Defang error'); add_section ($S, 'badheadersupp', 1, 'd', 'Bad header (debug supplemental)'); add_section ($S, 'fileoutputskipped', 0, 'd', 'File(1) output skipped'); add_section ($S, 'localdeliveryskipped', 1, 'd', 'Local delivery skipped'); add_section ($S, 'extramodules', 1, 'd', 'Extra code modules loaded at runtime'); add_section ($S, 'malwarebyscanner', 1, 'd', 'Malware by scanner'); add_section ($S, 'malwaretospam', 1, 'd', 'Malware to spam conversion'); add_section ($S, 'contenttype', 1, 'd', 'Content types'); add_section ($S, 'bayes', 1, 'd', 'Bayes probability'); add_section ($S, 'p0f', 1, 'd', 'p0f fingerprint'); add_section ($S, 'sadiags', 1, 'd', 'SpamAssassin diagnostics'); end_section_group ($S, 'misc'); print "build_sect_table: exit\n" if $Opts{'debug'} & D_SECT; } # XXX create array of defaults for detail <5, 5-9, >10 sub init_defaults() { map { $Opts{$_} = $Defaults{$_} unless exists $Opts{$_} } keys %Defaults; if (! $Opts{'standalone'}) { # LOGWATCH these take affect if no env present (eg. nothing in conf file) # 0 to 4 nostartinfo, notimings, nosarules, score_frequencies=0, score_percentiles=0, noautolearn # 5 to 9 nostartinfo, timings=95, sarules = 20 20, score_frequencies=defaults, score_percentiles=defaults, autolearn # 10 + startinfo, timings=100, sarules = all all score_frequencies=defaults, score_percentiles=defaults, autolearn if ($Opts{'detail'} < 5) { # detail 0 to 4, disable all supplemental reports $Opts{'autolearn'} = 0; #$Opts{'p0f'} = 0; $Opts{'timings'} = 0; $Opts{'sa_timings'} = 0; $Opts{'sarules'} = 0; $Opts{'startinfo'} = 0; $Opts{'score_frequencies'} = ''; $Opts{'score_percentiles'} = ''; } elsif ($Opts{'detail'} < 10) { # detail 5 to 9, disable startinfo report $Opts{'startinfo'} = 0; } else { # detail 10 and up, full reports #$Opts{'p0f'} = 'all all'; $Opts{'timings'} = 100; $Opts{'sa_timings'} = 100; $Opts{'sarules'} = 'all all'; } } } # Return a usage string, built from: # arg1 + # $usage_str + # a string built from each usable entry in the @Sections table. # sub usage($) { my $ret = ""; $ret = "@_\n" if ($_[0]); $ret .= $usage_str; my ($name, $desc); foreach my $sect (get_usable_sectvars(@Sections, 0)) { $name = lc $sect->{NAME}; $desc = $sect->{TITLE}; $ret .= sprintf " --%-38s%s\n", "$name" . ' LEVEL', "$desc"; } $ret .= "\n"; return $ret; } sub strip_trace($) { # at (eval 37) line 306, line 4. # at /usr/sbin/amavisd-maia line 2895, line 22. #$_[0] =~ s/ at \(.+\) line \d+(?:, \ line \d+)?\.$//; #$_[0] =~ s/ at (\S+) line \d+(?:, \ line \d+)?\.$/: $1/; while ($_[0] =~ s/ at (?:\(eval \d+\)|\S+) line \d+(?:, \ line \d+)?\.//) { ; } #print "strip_trace: \"$_[0]\"\n"; return $_[0]; } # Getopt helper, sets an option in Opts hash to one of three # values: its default, the specified value, or 0 if the option # was the "no" prefixed variant. # sub triway_opts ($ $) { my ($opt,$val) = @_; print "triway_opts: OPT: $opt, VAL: $val\n" if $Opts{'debug'} & D_ARGS; die "Option \"--${opt}\" requires an argument" if ($val =~ /^--/); if ($opt =~ s/^no//i) { $Opts{$opt} = 0; } elsif ('default' =~ /^${val}$/i) { $Opts{$opt} = $Defaults{$opt}; } else { $Opts{$opt} = $val; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et logwatch-7.14/scripts/services/automount0000644000175000017500000001305615075123620020755 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Gerald Teschl # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Gerald Teschl ######################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $MountAttempts = 0; #Init String containers my ( $Key, $ThisMount, $ThisOne ); #Init Hashes my ( %Failed, %FailedStartup, %KeyNotFound, %OtherList, %StartStop ); while (defined(my $ThisLine = )) { if ( ($ThisLine =~ /^using kernel protocol version .*$/) or ($ThisLine =~ /^expired .*$/) or ($ThisLine =~ /^lookup\(ldap\): got answer, but no first entry for /) or ($ThisLine =~ /^>>.*mount: .*$/) or ($ThisLine =~ /lookup_read_master: lookup\(nisplus\): couldn't locate? nis\+ table auto.master/) or ($ThisLine =~ /create_(tcp|udp)_client: hostname lookup failed: (No such process|Operation not permitted)/) or ($ThisLine =~ /lookup_mount: exports lookup failed for .*directory/) or ($ThisLine =~ /master_do_mount: failed to startup mount/) ) { # don't care about these } elsif ( ($ThisMount) = ($ThisLine =~ /^attempting to mount entry (.*)$/) ) { # store Mount #$Mount = $ThisMount; something is wrong with this -mgt $MountAttempts++; } elsif ($ThisLine =~ /^mount\(nfs\): nfs: mount failure .*:.* on (.*)$/) { $Failed{$1}{'nfsm'}++; } elsif ($ThisLine =~ /^mount\(nfs\): entry (.*) lookup failure$/) { $Failed{$1}{'nfsl'}++; } elsif (( $ThisLine =~ /^mount\(generic\): failed to mount .* on (.*)$/) or ( $ThisLine =~ /^handle_mounts: mount on (.*) failed!/) ) { $Failed{$1}{'mnt'}++; } elsif ( $ThisLine =~ /^failed to mount \/(\w+).*$/) { $Failed{$1}{'mnt'}++; } elsif ( ($ThisMount) = ( $ThisLine =~ /^(.*): mount failed!$/) ) { $FailedStartup{$ThisMount}++; } elsif ( $ThisLine =~ /^lookup\((file|program)\): lookup for (.*) failed$/) { $Failed{$2}{$1}++; } elsif ( ($ThisMount) = ($ThisLine =~ /^starting automounter version .* path = (.*), maptype = .*, mapname = .*$/) ) { $StartStop{$ThisMount}{'start'}++; $StartStop{$ThisMount}{'stop'}+=0; } elsif ( ($ThisMount) = ($ThisLine =~ /^shutting down, path = (.*)$/) ) { $StartStop{$ThisMount}{'stop'}++; } elsif ( ($Key) = ( $ThisLine =~ /^key "(.*)" not found in map source\(s\)\.$/) ) { $KeyNotFound{$Key}++; } else { # Report any unmatched entries... chomp($ThisLine); $OtherList{$ThisLine}++; } } if (keys %FailedStartup) { print "\nFailed Startups:\n"; foreach $ThisOne (keys %FailedStartup) { print " $ThisOne " . $FailedStartup{$ThisOne} . " Time(s)\n"; } } if (keys %Failed) { print "\nFailed mounts:\n"; foreach $ThisOne (sort keys %Failed) { printf (" %-20s", $ThisOne); if ($Failed{$ThisOne}{'nfsm'}) { print "NFS Mount Failure $Failed{$ThisOne}{'nfsm'} Time(s)"; } if ($Failed{$ThisOne}{'nfsl'}) { print "NFS Lookup Failure $Failed{$ThisOne}{'nfsl'} Time(s)"; } if ($Failed{$ThisOne}{'mnt'}) { print "Mount Failure $Failed{$ThisOne}{'mnt'} Time(s)"; } if ($Failed{$ThisOne}{'file'}) { print "File Lookup Failure $Failed{$ThisOne}{'file'} Time(s)"; } if ($Failed{$ThisOne}{'program'}) { print "Program Lookup Failure $Failed{$ThisOne}{'program'} Time(s)"; } print "\n"; } } if ( ($Detail >= 5) and (keys %KeyNotFound) ) { print "\nKeys not found:\n"; foreach $Key (keys %KeyNotFound) { print " $Key: $KeyNotFound{$Key} Time(s)\n"; } } if ( ($Detail >= 10) and (keys %StartStop) ) { print "\nStatistics:\n"; print " Total number of mount attempts: $MountAttempts\n"; foreach $ThisOne (keys %StartStop) { $StartStop{$ThisOne}{'start'} = 0 unless defined $StartStop{$ThisOne}{'start'}; $StartStop{$ThisOne}{'stop'} = 0 unless defined $StartStop{$ThisOne}{'stop'}; print " $ThisOne: Started $StartStop{$ThisOne}{'start'} and stopped $StartStop{$ThisOne}{'stop'} Time(s)\n"; } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach $ThisOne (keys %OtherList) { print "$ThisOne: $OtherList{$ThisOne} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/evtsystem0000644000175000017500000003764615075123620021000 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2008 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Ignore_messages = $ENV{'ignore_messages'} || '^$'; my %Errors; my %RestartRequired; my %Systems; my %Updates; my %UpdatesInstalled; my %UpdatesReadyForInstall; sub human() { my $b=$_; my $i; for ($i=0;$b>1024;$i++) { $b/=1024 } my $u=qw{B K M G T}[$i]; return "$b$u"; } while (defined(my $ThisLine = )) { # User specified ignore messages, lower cased next if $ThisLine =~ /$Ignore_messages/i; my ($Hostname,$Criticality,$SourceName,$DateTime,$EventID,$System,$UserName,$SIDType,$EventLogType,$CategoryString,$DataString,$ExpandedString,$Extra); #Determine format if ($ThisLine =~ /MSWinEventLog\[/) { # Snare 4 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$System,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\[(\d+)\]:(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } elsif ($ThisLine =~ /MSWinEventLog\t/) { # Snare 3 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$System,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\t(\d+)\t(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } if (!defined($Hostname)) { print STDERR "Cannot parse $ThisLine"; next; } #print STDERR "ExpandedString = $ExpandedString\n"; next if ($EventLogType eq "Verbose"); next if ($EventLogType eq "Information" and $Detail < 10); # Modify some items that prevent de-duplication if ($Detail < 10) { $ExpandedString =~ s/\d+ time\(s\)/XX times(s)/; $ExpandedString =~ s/requested by PID\s+\S+\s+//; $ExpandedString =~ s/processor \d+/processor X/; $ExpandedString =~ s/for \d+ seconds/for XX seconds/; $ExpandedString =~ s/(APPID|CLSID)\s+\{[0-9A-F\-]+\}/$1 {XXX}/g; $ExpandedString =~ s/(Time:) \d+:\d+:\d+\.\d+ \d+\/\d+\/\d+ Z/$1 TIME/g; while ($ExpandedString =~ /(\d{4,}) bytes/) { my $h = &human($1); $ExpandedString =~ s/$1 bytes/${h}b/g; } } if ($System eq "Application Popup") { #Ignore these next if $ExpandedString =~ /Initialization Failed : The application failed to initialize because the window station is shutting down/; next if $ExpandedString =~ /^Application popup: Windows : Other people are logged on to this computer. Shutting down Windows might cause them to lose data\. Do you want to continue shutting down\?$/; next if $ExpandedString =~ /^Application popup: Message from .*: Automatic software deployment is currently updating your system\. Please save all your documents as the the system might reboot without further notice\. Thank you\./; next if $ExpandedString =~ /^Application popup: Message from .*: The automated software installation utility has completed installing or updating software on your system\. No reboot was necessary\. All updates are complete\./; } if ($System eq "BTHUSB") { next if $ExpandedString =~ /^Windows cannot store Bluetooth authentication codes \(link keys\) on the local adapter\. Bluetooth keyboards might not work in the system BIOS during startup\.$/ and $Detail < 5; } if ($System =~ "EventLog") { #Ignore these next if $ExpandedString =~ /Microsoft \(R\) Windows \(R\) \d+\.\d+\. \d+ +(Multiprocessor Free|Service Pack \d)/; next if $ExpandedString =~ /^The Event log service was started./; next if $ExpandedString =~ /^The Event log service was stopped./; next if $ExpandedString =~ /^The system uptime is \d+ seconds/; } if ($System =~ "LsaSrv") { #Ignore these next if $ExpandedString =~ /^A logon cache entry for user .* was the oldest entry and was removed\. The timestamp of this entry was/; } if ($System eq "Microsoft-Windows-Application-Experience") { #Ignore these next if $ExpandedString eq "The Program Compatibility Assistant service successfully performed phase two initialization."; } if ($System eq "Microsoft-Windows-DfsSvc") { #Ignore these next if $ExpandedString =~ /^DFS has finished building all namespaces\.$/; next if $ExpandedString =~ /^DFS server has finished initializing\.$/; } if ($System eq "Microsoft-Windows-DNS-Client") { next if $ExpandedString =~ /^Name resolution for the name .* timed out/; next if $ExpandedString =~ /^The system failed to (?:register|update and remove) host .* resource records/; next if $ExpandedString =~ /^The system could not remove these host .* RRs/; } if ($System eq "Microsoft-Windows-FilterManager") { #Ignore these next if $ExpandedString =~ /^File System Filter .* has successfully loaded and registered with Filter Manager\.$/; } if ($System eq "Microsoft-Windows-Iphlpsvc") { #High Detail next if $ExpandedString =~ /^Isatap interface .* with address .* has been brought up\.$/ and $Detail < 10; next if $ExpandedString =~ /^Isatap interface .* is no longer active\.$/ and $Detail < 10; } if ($System eq "Microsoft-Windows-Kernel-Boot") { #High Detail next if $ExpandedString =~ /^The boot type was/ and $Detail < 10; next if $ExpandedString =~ /^The bootmgr spent .* waiting for user input/ and $Detail < 10; next if $ExpandedString =~ /^The last shutdown's success status was true. The last boot's success status was true\.$/ and $Detail < 10; next if $ExpandedString =~ /^There are .* boot options on this system/ and $Detail < 10; } if ($System eq "Microsoft-Windows-Kernel-General") { #High Detail next if $ExpandedString =~ /^The operating system started at system time/ and $Detail < 10; next if $ExpandedString =~ /^The operating system is shutting down at system time/ and $Detail < 10; next if $ExpandedString =~ /^The access history in hive .* was cleared updating \d+ keys/ and $Detail < 10; #TODO - We should warn is this is big next if $ExpandedString =~ /^The system time has changed to .* from/; } if ($System eq "Microsoft-Windows-Kernel-Power") { #High Detail next if $ExpandedString =~ /^The system is entering sleep/ and $Detail < 10; next if $ExpandedString =~ /^The kernel power manager has initiated a shutdown transition\.$/ and $Detail < 10; #Ignore these next if $ExpandedString =~ /^ACPI thermal zone .* has been enumerated/; next if $ExpandedString =~ /^Processor \d+ in group \d+ exposes the following power management capabilities/; } if ($System eq "Microsoft-Windows-Kernel-Processor-Power") { #Ignore these next if $ExpandedString =~ /^Processor \d+ in group \d+ exposes the following/; } if ($System eq "Microsoft-Windows-GroupPolicy") { #Ignore these next if $ExpandedString =~ /^The Group Policy settings for the (computer|user) were processed successfully\. There were no changes detected since the last successful processing of Group Policy\.$/; next if $ExpandedString =~ /^The Group Policy settings for the (computer|user) were processed successfully\. New settings from \d+ Group Policy objects were detected and applied\.$/ and $Detail == 0; } if ($System eq "Microsoft-Windows-Ntfs") { #Ignore these next if $ExpandedString =~ /^Volume .* is healthy\. No action is needed\.$/; } if ($System eq "Microsoft-Windows-Power-Troubleshooter") { #High Detail next if $ExpandedString =~ /^The system has resumed from sleep/ and $Detail < 10; } if ($System eq "Microsoft-Windows-Time-Service") { #High Detail next if $ExpandedString =~ /^The time provider NtpClient is currently receiving valid time data from/ and $Detail < 10; next if $ExpandedString =~ /^The time service is now synchronizing the system time with the time source/ and $Detail < 10; } if ($System eq "Microsoft-Windows-WAS") { #High Detail next if $ExpandedString =~ /^A worker process with process id of .* serving application pool .* has requested a recycle because the worker process reached its allowed processing time limit/ and $Detail < 10; } if ($System eq "Microsoft-Windows-WindowsUpdateClient" or $System eq "Windows Update Agent") { #High Detail next if $ExpandedString =~ /^Automatic Updates is now paused\.$/ and $Detail < 10; next if $ExpandedString =~ /^Windows Update started downloading an update\.$/ and $Detail < 10; #Updates if (my ($InstallDateTime, $Updates) = $ExpandedString =~ /^Installation Ready: The following updates are downloaded and ready for installation(?:\. )?(?:This computer is currently scheduled to install these updates on (.*)|To install the updates, an administrator should log on.*|): - (.*)$/) { $InstallDateTime =~ s/\?//g; foreach my $Update (split(" - ",$Updates)) { $InstallDateTime = "Now" if $InstallDateTime eq ""; $UpdatesReadyForInstall{$Hostname}->{$Update} = $InstallDateTime; } next; } if (my ($Update) = $ExpandedString =~ /^Installation Successful: Windows successfully installed the following update: (.*)$/) { delete($UpdatesReadyForInstall{$Hostname}->{$Update}); push(@{$UpdatesInstalled{$Hostname}},$Update); next; } if ($ExpandedString =~ /^Installation Failure:/) { $Errors{$System}->{"$Hostname $ExpandedString"}++; next; } if ($ExpandedString =~ /^Installation Started:/) { next; } if ($ExpandedString =~ /^Restart Required:/) { $RestartRequired{$Hostname} = 1; next; } } if ($System eq "Microsoft-Windows-WHEA-Logger") { $Errors{$System}->{"$Hostname $ExpandedString"}++; next; } if ($System eq "Microsoft-Windows-Winlogon") { #High Detail next if $ExpandedString =~ /User \w+ Notification for Customer Experience Improvement Program/ and $Detail < 10; } if ($System eq "Microsoft-Windows-WinRM") { #High Detail next if $ExpandedString =~ /^The WinRM service is listening for WS-Management requests/ and $Detail < 10; } if ($System eq "NPS") { #High Detail next if $ExpandedString =~ /^A LDAP connection with domain controller .* for domain .* is established/ and $Detail < 10; } if ($System eq "Service Control Manager") { #Ignore these next if $ExpandedString =~ /^The (.*) service entered the running state\./; next if $ExpandedString =~ /^The (.*) service entered the stopped state\./; next if $ExpandedString =~ /^The (.*) service was successfully sent a start control\./; next if $ExpandedString =~ /^The (.*) service was successfully sent a stop control\./; } if ($System eq "USER32") { #High Detail next if $ExpandedString =~ /^The process .* has initiated the power off of computer \w+ on behalf of user .* for the following reason: .*$/ and $Detail < 10; } if ($System eq "Virtual Disk Service") { #High Detail next if $ExpandedString =~ /Service (started|stopped)/ and $Detail < 10; } if ($System eq "atikmdag") { #Ignore these next if $ExpandedString =~ /^UVD Information$/; #High Detail next if $ExpandedString =~ /^Display is not active$/ and $Detail < 10; } if ($System eq "volsnap") { #Med Detail next if $ExpandedString =~ /^The oldest shadow copy of volume .* was deleted to keep disk space usage for shadow copies of volume .* below the user defined limit\.$/ and $Detail < 5; } next if $ExpandedString =~ /client service is started$/ and $Detail < 10; next if $ExpandedString =~ /started successfully\.$/ and $Detail < 10; next if $ExpandedString =~ /has successfully (?:started|stopped)\./ and $Detail < 10; next if $ExpandedString =~ /service .* (?:started|stopped)/i and $Detail < 10; next if $ExpandedString =~ /Module has (?:started|stopped)/ and $Detail < 10; next if $ExpandedString =~ /Driver initialized successfully\.$/ and $Detail < 10; next if $ExpandedString =~ /Network controller configured for .* link\.$/ and $Detail < 10; next if $ExpandedString =~ /Network link has been established/ and $Detail < 10; next if $ExpandedString =~ /^The driver package installation has succeeded\.$/ and $Detail < 10; next if $ExpandedString =~ /^The .* service entered the .* state/ and $Detail < 10; next if $ExpandedString =~ /^The process .* has initiated the (?:power off|restart|shutdown) of computer .* on behalf of user .* for the following reason/ and $Detail < 5; next if $ExpandedString =~ /^UVD Information$/; next if $ExpandedString =~ /Link has been established:/; # Add to the list $Systems{$System}->{"$Hostname $ExpandedString"}++; } # Handle high priority errors first my $System = "Microsoft-Windows-WER-SystemErrorReporting"; if (defined($Systems{$System})) { print "\nSYSTEM ERRORS!:\n"; foreach my $Error (sort(keys %{$Systems{$System}})) { print " $Error : $Systems{$System}->{$Error} Times\n"; } delete($Systems{$System}); } print "\n"; # Next output items marked as errors if (keys %Errors) { print "\nERRORS:"; foreach my $System (sort(keys %Errors)) { print "\n $System\n"; foreach my $Error (sort(keys %{$Errors{$System}})) { print " $Error : $Errors{$System}->{$Error} Times\n"; } } } if (keys %Systems) { foreach my $System (sort(keys %Systems)) { print "\n$System\n"; foreach my $Error (sort(keys %{$Systems{$System}})) { print " $Error : $Systems{$System}->{$Error} Times\n"; } } } if (keys %UpdatesReadyForInstall or keys %UpdatesInstalled) { print "\nWindows Update Summary:\n"; foreach my $Hostname (sort(keys %UpdatesReadyForInstall)) { # We may have removed all updates from this list when installed if (keys %{$UpdatesReadyForInstall{$Hostname}}) { print " Updates ready for install on $Hostname:\n"; foreach my $Update (sort(keys %{$UpdatesReadyForInstall{$Hostname}})) { print " $Update at $UpdatesReadyForInstall{$Hostname}->{$Update}\n"; } } } print "\n" if keys %UpdatesReadyForInstall; foreach my $Hostname (sort(keys %UpdatesInstalled)) { print " Updates successfully installed on $Hostname:\n"; foreach my $Update (@{$UpdatesInstalled{$Hostname}}) { print " $Update\n"; } } print "\n Restart required on hosts: " if keys %RestartRequired; foreach my $Hostname (sort(keys %RestartRequired)) { print "$Hostname "; } print "\n"; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/lvm0000644000175000017500000001416615075123620017523 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2014-2019 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use warnings; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $PoolThreshold = $ENV{'pool_threshold'} || 0; my $PoolMetadataThreshold = $ENV{'pool_metadata_threshold'} || 0; my %Active; my %PoolUsed; my %PoolMetadataUsed; my $SnapshotThreshold = $ENV{'snapshot_threshold'} || 0; my %SnapshotUsed; my %MonitoringOn; my %MonitoringOff; my %MonitoringSnapshot; my %MonitoringSnapshotOff; my %Resize; my %OtherList; while (defined(my $ThisLine = )) { chomp($ThisLine); # Seeing leading space on Fedora 26 $ThisLine =~ s/^ *//; if ($ThisLine =~ /^(pvscan\[\d+\] )?PV .* online(?:|, VG .* is complete)\.$/ or $ThisLine =~ /(pvscan\[\d+\] )?activating all complete VGs for init/ or $ThisLine =~ /(pvscan\[\d+\] )?PVID .* read from .* last written to/ or $ThisLine =~ /(pvscan\[\d+\] )?VG .* not using quick activation/ or $ThisLine =~ /(pvscan\[\d+\] )?VG .* run autoactivation/ # This happens often at startup or $ThisLine =~ /WARNING: lvmetad is being updated, retrying/ # This happens on shutdown or $ThisLine =~ /dmeventd detected break while being idle for 0 second\(s\), exiting/ # This happens when dmeventd autostarted or $ThisLine =~ /dmeventd ready for processing\.$/ or $ThisLine =~ /dmeventd shutting down\.$/ or $ThisLine =~ /dmeventd was idle for .*, exiting\.$/ # Misc cleanups or $ThisLine =~ /Logical volume .* successfully resized/ ) { # Ignore } elsif ($ThisLine =~ /^(?:WARNING: )?Thin (\S+) is now (\d+(\.\d+)?)% full/) { $PoolUsed{$1} = $2 if $2 >= $PoolThreshold; } elsif ($ThisLine =~ /^(?:WARNING: )?Thin metadata (\S+) is now (\d+(\.\d+)?)% full/) { $PoolMetadataUsed{$1} = $2 if $2 >= $PoolMetadataThreshold; } elsif ($ThisLine =~ /^Monitoring thin pool (\S+)\./) { $MonitoringOn{$1}++; } elsif ($ThisLine =~ /^Monitoring snapshot (\S+)\./) { $MonitoringSnapshot{$1}++; } elsif ($ThisLine =~ /^No longer monitoring thin pool (\S+)\./) { $MonitoringOff{$1}++; } elsif ($ThisLine =~ /^No longer monitoring snapshot (\S+)\./) { $MonitoringSnapshotOff{$1}++; } elsif ($ThisLine =~ /^(?:WARNING: )?Snapshot (\S+) is now (\d+(\.\d+)?)% full/) { $SnapshotUsed{$1} = $2 if $2 >= $SnapshotThreshold; } elsif ($ThisLine =~ /^(\d+) logical volume\(s\) in volume group "(\S+)" monitored/) { $MonitoringOn{$2}++; } elsif ($ThisLine =~ /^(\d+) logical volume\(s\) in volume group "(\S+)" unmonitored/) { $MonitoringOff{$2}++; } elsif ($ThisLine =~ /^(\d+) logical volume\(s\) in volume group "(\S+)" now active/) { $Active{$2}=$1; } elsif ($ThisLine =~ /^Size of logical volume (\S+) changed from (.*) to (.*)\.$/) { $Resize{$1}="$3" } else { $OtherList{$ThisLine}++; } } if (keys %PoolUsed) { print "Thin Pool Usage:\n"; foreach my $Pool (sort {$a cmp $b} keys %PoolUsed) { print " $Pool: $PoolUsed{$Pool}% full\n"; } print "\n"; } if (keys %PoolMetadataUsed) { print "Thin Pool Metadata Usage:\n"; foreach my $Pool (sort {$a cmp $b} keys %PoolMetadataUsed) { print " $Pool: $PoolMetadataUsed{$Pool}% full\n"; } print "\n"; } if (keys %SnapshotUsed) { print "Snapshot Usage:\n"; foreach my $Snapshot (sort {$a cmp $b} keys %SnapshotUsed) { print " $Snapshot: $SnapshotUsed{$Snapshot}% full\n"; } print "\n"; } if (keys %Resize) { print "Resize snapshot:\n"; foreach my $Snapshot (sort {$a cmp $b} keys %Resize) { print " $Snapshot: $Resize{$Snapshot}\n"; } print "\n"; } if (keys %Active and $Detail) { print "LVM active:\n"; foreach my $VG (sort {$a cmp $b} keys %MonitoringOn) { print " $VG: $Active{$VG} logical volume(s)\n"; } print "\n"; } if (keys %MonitoringOn and $Detail) { print "Monitoring started for:\n"; foreach my $Pool (sort {$a cmp $b} keys %MonitoringOn) { print " $Pool: $MonitoringOn{$Pool} Time(s)\n"; } print "\n"; } if (keys %MonitoringOff and $Detail) { print "Monitoring stopped for:\n"; foreach my $Pool (sort {$a cmp $b} keys %MonitoringOff) { print " $Pool: $MonitoringOff{$Pool} Time(s)\n"; } print "\n"; } if (keys %MonitoringSnapshot and $Detail) { print "Monitoring snapshot:\n"; foreach my $Snapshot (sort {$a cmp $b} keys %MonitoringSnapshot) { print " $Snapshot: $MonitoringSnapshot{$Snapshot} Time(s)\n"; } print "\n"; } if (keys %MonitoringSnapshotOff and $Detail) { print "Monitoring stopped for snapshot:\n"; foreach my $Snapshot (sort {$a cmp $b} keys %MonitoringSnapshotOff) { print " $Snapshot: $MonitoringSnapshotOff{$Snapshot} Time(s)\n"; } print "\n"; } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/vpopmail0000644000175000017500000001002015075123620020535 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # Written & Maintained by Chris Smith (csmith@squiz.net) ########################################################################## ####################################################### ## Copyright (c) 2008 Chris Smith ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $IgnoreUnmatched = $ENV{'vpopmail_ignore_unmatched'} || 0; my $ShowSuccessfulLogins = $ENV{'successful_logins'}; #Obj style hash counter/container my $SuccessfulLogin; #Init string containers my ( $Account, $Bounce, $Email, $IP, $NoUser, $Type, $VirtAccount ); #Init arrays my @OtherList = (); #Init Hashes my ( %NoAccount, %NoUserFound, %PasswordFail, ); while (defined(my $ThisLine = )) { if ( ( $ThisLine =~ /vpop_mail/ ) ) { # We don't care about these } elsif (($VirtAccount) = ($ThisLine =~ /no virt found (.*?)\:/i )) { $NoAccount{$VirtAccount}++; } elsif (($Account,$IP) = ($ThisLine =~ /login success (.*?)\:(.*)/i )) { $Type = 'Plain'; $SuccessfulLogin->{$Account}->{$Type}->{$IP}++; } elsif (($Type,$Account,$IP) = ($ThisLine =~ /\((.*?)\) login success (.*?)\:(.*)/i )) { $SuccessfulLogin->{$Account}->{$Type}->{$IP}++; } elsif (($ThisLine =~ /bounce msg/)) { $Bounce++; } elsif (($Email) = ($ThisLine =~ /password fail (.*?)\:/i )) { $PasswordFail{$Email}++; } elsif ((undef, $NoUser) = ($ThisLine =~ /(no user found|user not found) (.*?)\:/i )) { $NoUserFound{$NoUser}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } if ($ShowSuccessfulLogins) { if (keys %{$SuccessfulLogin}) { print "\nSuccessful Logins:\n"; foreach my$Line (sort {$a cmp $b} keys %{$SuccessfulLogin}) { foreach my $Type (sort {$a cmp $b} keys %{$SuccessfulLogin->{$Line}}) { foreach my $Detail (sort {$a cmp $b} keys %{$SuccessfulLogin->{$Line}->{$Type}}) { print "\t".$Line." ( from " . $Detail . " using " . $Type . " ) - " . $SuccessfulLogin->{$Line}->{$Type}->{$Detail} . " Time(s)\n"; } } } } } if ( (keys %PasswordFail) ) { print "\nPassword Failures:\n"; foreach my $Line (sort {$a cmp $b} keys %PasswordFail) { print "\t" . $Line . " - ". $PasswordFail{$Line} . " Time(s)\n"; } } if ( (keys %NoAccount) ) { print "\nNo Account Found:\n"; foreach my $Line (sort {$a cmp $b} keys %NoAccount) { print "\t" . $Line . " - ". $NoAccount{$Line} . " Time(s)\n"; } } if ( (keys %NoUserFound) ) { print "\nNo Such User Found:\n"; foreach my $Line (sort {$a cmp $b} keys %NoUserFound) { print "\t" . $Line . " - ". $NoUserFound{$Line} . " Time(s)\n"; } } if ($Bounce) { print "\nBounced messages: ". $Bounce."\n"; } if (($#OtherList >= 0) and (not $IgnoreUnmatched)){ print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/kernel0000644000175000017500000002661615075123620020210 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ##################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use Logwatch ':ip'; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Ignore_cpu_throttled = $ENV{'ignore_cpu_throttled'} || 0; my $Ignore_faults = $ENV{'ignore_faults'}; my $Ignore_rpcsec_expired = $ENV{'ignore_rpcsec_expired'} || 0; my $Ignore_messages = $ENV{'kernel_ignore_messages'} || '^$'; my $CPUThrottled = 0; my %SYNflood = (); my %RAIDErrors = (); my %DRBDErrors = (); my %SegFaults = (); my %GPFaults = (); my %TrapInt3s = (); my %UnalignedErrors = (); my %FPAssists = (); my %OOM = (); my %Errors = (); my %Kernel = (); my %EDACs = (); my %NFS = (); my %EXT4Volume = (); my %EXT4 = (); #Init String Containers my ( $Volume, $errormsg, $from, $host, $on ); while (defined(my $ThisLine = )) { chomp($ThisLine); next if ($ThisLine eq ''); # Remove timestamp if present $ThisLine =~ s/^\[\s*\d+\.\d+\]\s*//; if ( # filter out audit messages - these should be parsed by the audit # service ($ThisLine =~ /(type=\d+\s+)?audit\(/) # following now in iptables service or ($ThisLine =~ /^Packet log: .*PROTO=/) or ($ThisLine =~ /IN=.*OUT=.*SRC=.*DST=.*PROTO=/) or ($ThisLine =~ /RAS: Correctable Errors collector initialized/) # user specified ignore messages, lower cased or ($ThisLine =~ /$Ignore_messages/i) ) { # ignore the above strings } elsif ( ($from,$on) = ( $ThisLine =~ /^Warning: possible SYN flood from ([^ ]+) on ([^ ]+):.+ Sending cookies/ ) ) { my $Fullfrom = LookupIP($from); my $Fullon = LookupIP($on); $SYNflood{$Fullon}{$Fullfrom}++; } elsif ($ThisLine =~ /temperature above threshold, cpu clock throttled/) { $CPUThrottled++ if not $Ignore_cpu_throttled; } elsif ($ThisLine =~ /continuing in degraded mode/) { $RAIDErrors{$ThisLine}++; } elsif ($ThisLine =~ /([^(]*)\[\d+\]: segfault at/) { $SegFaults{$1}++; } elsif ($ThisLine =~ /([^(]*)\[\d+\] general protection/) { $GPFaults{$1}++; } elsif ($ThisLine =~ /([^(]*)\[\d+\] trap int3 /) { $TrapInt3s{$1}++; } elsif ($ThisLine =~ /([^(]*)\(\d+\): unaligned access to/) { $UnalignedErrors{$1}++; } elsif ($ThisLine =~ /([^(]*)\(\d+\): floating-point assist fault at ip/) { $FPAssists{$1}++; } elsif ($ThisLine =~ /(?:[Kk]illed|[Kk]ill) process \d+ \((.*)\)/) { $OOM{$1}++; } elsif ($ThisLine =~ /(EDAC (?:igen6 )?(?:MC|PCI)\d:.*)/) { # Standard boot messages next if $ThisLine =~ /(?:Giving out device to |HANDLING IBECC MEMORY |MC\d+: ADDR 0x[0-9a-f]* *$)/; $EDACs{$1}++; } elsif ($ThisLine =~ /(block drbd\d+): Online verify found (\d+) \d+k block out of sync/) { $DRBDErrors{$1}{"$2 block(s) out of sync"} = 1; } elsif ($ThisLine =~ /(block drbd\d+): \[.*\] sock_sendmsg time expired/) { $DRBDErrors{$1}{"sock_sendmsg time expired"}++; } elsif ($ThisLine =~ /(block drbd\d+): Began resync as (SyncSource|SyncTarget)/) { $DRBDErrors{$1}{"Began resync as $2"}++; } elsif ( $ThisLine =~ /raid.*CRIT/) { # kernel: megaraid_sas 0000:88:00.0: 781934 (727946738s/0x0004/CRIT) - Enclosure PD 08(c Port 0 - 3/p1) phy bad for slot 19 # de-dupe $ThisLine =~ s/: \d+ /: /; $ThisLine =~ s/\(\d+s\//(Xs\//; $Errors{$ThisLine}++; } elsif ( ( $errormsg ) = ( $ThisLine =~ /(.*?error.{0,17})/i ) ) { # filter out smb open/read errors cased by insufficient permissions my $SkipError = 0; $SkipError = 1 if $ThisLine =~ /smb_readpage_sync: .*open failed, error=-13/; $SkipError = 1 if $ThisLine =~ /smb_open: .* open failed, result=-13/; $SkipError = 1 if $ThisLine =~ /smb_open: .* open failed, error=-13/; # filter out error_exit in stack traces caused by OOM conditions $SkipError = 1 if $ThisLine =~ /\[<[\da-f]+>\] error_exit\+0x/; # These are informative, not errors $SkipError = 1 if $ThisLine =~ /ACPI _OSC request failed \(AE_ERROR\), returned control mask: 0x1d/; $SkipError = 1 if $ThisLine =~ /ERST: Error Record Serialization Table \(ERST\) support is initialized/; $SkipError = 1 if $ThisLine =~ /GHES: Generic hardware error source: \d+ notified via .* is not supported/; $SkipError = 1 if $ThisLine =~ /HEST: Enabling Firmware First mode for corrected errors/; $SkipError = 1 if $ThisLine =~ /PCIe errors handled by (?:BIOS|OS)/; # These happen when kerberos tickets expire, which can be normal $SkipError = 1 if $ThisLine =~ /Error: state manager encountered RPCSEC_GSS session expired against NFSv4 server/ && $Ignore_rpcsec_expired; $SkipError = 1 if $ThisLine =~ /RAS: Correctable Errors collector initialized/; # filter out mount options $SkipError = 1 if $ThisLine =~ /errors=(?:continue|remount-ro|panic)/; $Errors{$errormsg}++ if ( (! $SkipError) || ($Detail > 8)); } elsif ( ( ($Volume, $errormsg) ) = ( $ThisLine =~ /^EXT4-fs \(([^)]+)\): (.*)/ ) ) { if ($errormsg =~ /INFO: recovery required on readonly filesystem/) { $EXT4Volume{$Volume} = 1; push @{$EXT4{$Volume}}, $errormsg; } elsif ($EXT4Volume{$Volume}) { push @{$EXT4{$Volume}}, $errormsg if $EXT4{$Volume}; delete $EXT4Volume{$Volume} if ($errormsg =~ /recovery complete/); } } elsif ( ( $errormsg ) = ( $ThisLine =~ /((BUG|WARNING|INFO):.{0,40})/ ) ) { $Errors{$errormsg}++; } elsif ( ( $host ) = ( $ThisLine =~ /^nfs: server (.*) not responding/ ) ) { $NFS{$host}++; # OTHER } else { # XXX For now, going to ignore all other kernel messages as there # XXX are practically an infinite number and most of them are obviously # XXX not parsed here at this time. # filter out smb open/read errors cased by insufficient permissions my $SkipError = 0; $SkipError = 1 if $ThisLine =~ /smb_readpage_sync: .*open failed, error=-13/; $SkipError = 1 if $ThisLine =~ /smb_open: .* open failed, result=-13/; $SkipError = 1 if $ThisLine =~ /smb_open: .* open failed, error=-13/; $SkipError = 1 if $ThisLine =~ /block drbd\d+: Out of sync: start=\d+/; $SkipError = 1 if $ThisLine =~ /block drbd\d+: updated( sync)? UUIDs?/i; $SkipError = 1 if $ThisLine =~ /block drbd\d+: Resync done/; $SkipError = 1 if $ThisLine =~ /block drbd\d+: cs:(?:Ahead|Behind) rs_left/; $SkipError = 1 if $ThisLine =~ /block drbd\d+: \d+ % had equal checksums, eliminated:/; $Kernel{$ThisLine}++ if ( (! $SkipError) || ($Detail > 8)) ; } } if (keys %SYNflood) { print "\nWarning: SYN flood on:\n"; foreach my $Thisone (sort {$a cmp $b} keys %SYNflood) { print " " . $Thisone . " from:\n"; foreach my $Next (sort {$a cmp $b} keys %{$SYNflood{$Thisone}}) { print " " . $Next . ": $SYNflood{$Thisone}{$Next} Time(s)\n"; } } } if (keys %RAIDErrors) { print "\nWARNING: RAID Errors Present\n"; foreach my $Thisone ( sort {$a cmp $b} keys %RAIDErrors ) { print " $Thisone ...: $RAIDErrors{$Thisone} Time(s)\n"; } } if (keys %DRBDErrors) { print "\nWARNING: DRBD Errors Present\n"; foreach my $Thisone ( sort {$a cmp $b} keys %DRBDErrors ) { foreach my $Msg (sort {$a cmp $b} keys %{$DRBDErrors{$Thisone}}) { print " $Thisone: $Msg"; print " : $DRBDErrors{$Thisone}{$Msg} Time(s)" if $DRBDErrors{$Thisone}{$Msg} > 1; print "\n"; } } } if (keys %SegFaults) { my $header_printed=0; foreach my $Thisone ( sort {$a cmp $b} keys %SegFaults ) { if ($Thisone =~ /^$Ignore_faults$/) { next; } if (!$header_printed) { print "\nWARNING: Segmentation Faults in these executables\n"; $header_printed=1; } print " $Thisone : $SegFaults{$Thisone} Time(s)\n"; } } if (keys %GPFaults) { my $header_printed=0; foreach my $Thisone ( sort {$a cmp $b} keys %GPFaults ) { if ($Thisone =~ /^$Ignore_faults$/) { next; } if (!$header_printed) { print "\nWARNING: General Protection Faults in these executables\n"; $header_printed=1; } print " $Thisone : $GPFaults{$Thisone} Time(s)\n"; } } if (keys %TrapInt3s) { my $header_printed=0; foreach my $Thisone ( sort {$a cmp $b} keys %TrapInt3s ) { if ($Ignore_faults =~ /\b\Q$Thisone\E\b/i) { next; } if (!$header_printed) { print "\nWARNING: Trap int3 in these executables\n"; $header_printed=1; } print " $Thisone : $TrapInt3s{$Thisone} Time(s)\n"; } } if (keys %UnalignedErrors) { print "\nWARNING: Unaligned Errors in these executables\n"; foreach my $Thisone ( sort {$a cmp $b} keys %UnalignedErrors ) { print " $Thisone : $UnalignedErrors{$Thisone} Time(s)\n"; } } if (keys %FPAssists) { print "\nWARNING: Floating-Point Assists in these executables\n"; foreach my $Thisone ( sort {$a cmp $b} keys %FPAssists ) { print " $Thisone : $FPAssists{$Thisone} Time(s)\n"; } } if (keys %OOM) { print "\nWARNING: Out of memory killer killed these executables\n"; foreach my $Thisone ( sort {$a cmp $b} keys %OOM ) { print " $Thisone : $OOM{$Thisone} Time(s)\n"; } } if (keys %Errors) { print "\nWARNING: Kernel Errors Present\n"; foreach my $Thisone ( sort {$a cmp $b} keys %Errors ) { print " $Thisone ...: $Errors{$Thisone} Time(s)\n"; } } if (keys %EDACs) { print "\nWARNING: Kernel EDAC Messages\n"; foreach my $Thisone ( sort {$a cmp $b} keys %EDACs ) { print " $Thisone ...: $EDACs{$Thisone} Time(s)\n"; } } if (keys %NFS) { print "\nWARNING: NFS Server Not Responding Messages\n"; foreach my $Thisone ( sort {$a cmp $b} keys %NFS ) { print " $Thisone ...: $NFS{$Thisone} Time(s)\n"; } } if (keys %EXT4) { print "\nWARNING: Ext4 Errors Present\n"; foreach my $Volume ( sort {$a cmp $b} keys %EXT4 ) { print " $Volume:\n"; foreach my $Msg (@{$EXT4{$Volume}}) { print " $Msg\n"; } } } if ($CPUThrottled) { print "\nWARNING: CPU Package temperature above threshold, cpu clock throttled $CPUThrottled Time(s)\n"; } # OTHER if ( ($Detail >= 5) and (keys %Kernel) ) { print "\n"; foreach my $Thisone (sort {$a cmp $b} keys %Kernel) { print $Kernel{$Thisone} . " Time(s): " . $Thisone . "\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/imapd0000644000175000017500000003142515075123620020014 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # The imap script was written by: # Paweł Gołaszewski ######################################################## ##################################################### ## Copyright (c) 2008 Paweł Gołaszewski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'}; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'}; if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG \n\n"; } #Init String Containers my ( $Conns, $CountLength, $CountSpaceLength, $Down, $DownLength, $DownSpaceLength, $HostCount, $HostLength, $HostSpaceLength, $NonSSLCount, $NonSSLLength, $NonSSLSpaceLength, $SSL, $SSLCount, $SSLLength, $SSLSpaceLength, $Size, $SizeLength, $SizeSpaceLength, $TotalCount, $TotalLength, $TotalSpaceLength, $UserLength, $UserSpaceLength, $totalSpaceLength, $DownloadSize1, $DownloadSize2, $Host, $Num, $Reason, $User, ); #Init Hashes my ( %AutoLogout, %Connection, %ConnectionNonSSL, %ConnectionSSL, %DownloadedMessagesSize, %KilledSession, %Login, %LoginFailed, %Logout, %Logout2, %OtherList, %SocketErrors ); while (defined(my $ThisLine = )) { if ( ($ThisLine =~ /^Initializing */) or ($ThisLine =~ /^spgetpwnam: can't find user: */) or ($ThisLine =~ /^couriertls: read: Connection reset by peer/ ) or # timeouts are reported in some other scripts - maybe it should be here too? ($ThisLine =~ /^couriertls: read: Connection timed out/ ) or ($ThisLine =~ /^LOGOUT, ip=\[.*\], rcvd=\d+, sent=\d+$/) or ($ThisLine =~ /^Disconnected, ip=\[.*\]/) or # uw-imapd ($ThisLine =~ /^Moved \d+ bytes of new mail to.*$/) or ($ThisLine =~ /^Unexpected client disconnect, while reading line.*$/) or ($ThisLine =~ /^ip=\[.*\], An unexpected TLS packet was received.*$/) or ($ThisLine =~ /^ip=\[.*\], Unexpected SSL connection shutdown.*$/) ) { # Don't care about these... } elsif ( ($User, $Host) = ( $ThisLine =~ /^Login user=(.*?) host=(.*\[.*\])$/ ) ) { $Login{$User}{$Host}++; } elsif ( ($User, $Host) = ( $ThisLine =~ /^LOGIN, user=(.*?), ip=\[([^ ,]+)\](?:, port=\[\d+\])?, protocol=IMAP$/o ) ) { $Login{$User}{$Host}++; } elsif ( ($User,$Host) = ( $ThisLine =~ /^Authenticated user=(.*) host=(.*\[.*\]).*$/ ) ) { $Login{$User}{$Host}++; } elsif ( ($User,$Host) = ( $ThisLine =~ /^Preauthenticated user=(.*) host=(.*)$/ ) ) { $Login{$User}{$Host}++; } elsif ( ($Host) = ( $ThisLine =~ /^imap service init from (.*)$/ ) ) { $ConnectionNonSSL{$Host}++; $Connection{$Host}++; } elsif ( ($Host) = ( $ThisLine =~ /^imaps SSL service init from (.*)$/ ) ) { $ConnectionSSL{$Host}++; $Connection{$Host}++; } elsif ( ($Host) = ( $ThisLine =~ /^Connection, ip=\[(.*)\]$/o ) ) { $Connection{$Host}++; } elsif ( ($Num, $Host) = ( $ThisLine =~ /^message repeated (.*) times: \[ Connection, ip=\[(.*)\]$/o ) ) { $Connection{$Host} += $Num; # } elsif ( ($User,$Downloaded,$DownloadSize,$Left,$LeftSize) = ( $ThisLine =~ /^Stats: (.*?) (.*?) (.*?) (.*?) (.*?)$/) ) { # $DownloadedMessages{$User} += $Downloaded; # $DownloadedMessagesSize{$User} += $DownloadSize; # $MessagesLeft{$User} = $Left; # $MboxSize{$User} = $LeftSize; # } elsif ( ($User,$Host) = ( $ThisLine =~ /^authentication failed for user (.*?) - (.*)/ ) ) { # $LoginFailed{"$Host ($User)"}++; } elsif ( ($User, $Host) = ( $ThisLine =~ /^Logout user=(.*?) host=(.*\[.*\])$/) ) { $Logout{$User}{$Host}++; $Logout2{$User}++; # More generic pattern for uw-imapd } elsif ( ($User, $Host) = ( $ThisLine =~ /^Logout user=(.*?) host=(.*)$/) ) { $Logout{$User}{$Host}++; $Logout2{$User}++; } elsif ( ($User, $Host, $DownloadSize1, $DownloadSize2) = ( $ThisLine =~ /^(?:LOGOUT|TIMEOUT|DISCONNECTED), user=(.*?), ip=\[([^ ,]+)\](?:, port=\[\d+\])?, headers=(\d+), body=(\d+)/o ) ) { $Logout{$User}{$Host}++; $Logout2{$User}++; $DownloadedMessagesSize{$User} += $DownloadSize1 + $DownloadSize2; if ( ( $ThisLine =~ /, starttls=1/o ) ) { $ConnectionSSL{$Host}++; } else { $Connection{$Host}++; } } elsif ( ($User,$Host) = ( $ThisLine =~ /^Autologout user=(.*) host=(.*\[.*\])$/ ) ) { $AutoLogout{$User}{$Host}++; $Logout{$User}{$Host}++; $Logout2{$User}++; } elsif ( ($Reason,$User,$Host) = ( $ThisLine =~ /^Killed (.*) user=(.*) host=(.*\[.*\])$/ ) ) { $Logout{$User}{$Host}++; $Logout2{$User}++; $KilledSession{$User}{$Reason}++; } elsif ( ( ($User,$Host) = ( $ThisLine =~ /^Broken pipe, while reading line user=(.*) host=(.*\[.*\])$/ )) or ( ($User,$Host) = ( $ThisLine =~ /^Command stream end of file, while reading line user=(.*) host=(.*\[.*\])$/ )) or ( ($User,$Host) = ( $ThisLine =~ /^Connection (?:reset by peer|timed out), while reading line user=(.*) host=(.*\[.*\])$/ )) or ( ($User,$Host) = ( $ThisLine =~ /^No route to host, while reading line user=(.*) host=(.*\[.*\])$/ )) or ( ($User,$Host) = ( $ThisLine =~ /^Unexpected client disconnect, while reading line user=(.*) host=(.*\[.*\])$/ )) ) { $Logout{$User}{$Host}++; $Logout2{$User}++; $SocketErrors{$Host}++; } elsif ( (( $ThisLine =~ /^.*error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number.*$/)) or (( $ThisLine =~ /^.*error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher.*$/)) ) { $SocketErrors{'unknown'}++; } else { # Report any unmatched entries... # remove PID from named messages $ThisLine =~ s/^(client [.0-9]+)\S+/$1/; chomp($ThisLine); $OtherList{$ThisLine}++; } } ################################################ if ( ( $Detail >= 0 ) and (keys %LoginFailed)) { print "\n\n[IMAPd] Login failures:". "\n=========================". "\n Host (user) | # ". "\n------------------------------------------------------------- | -----------"; my $ConnCount = 0; foreach my $Host (sort keys %LoginFailed) { $Conns = $LoginFailed{$Host}; $HostLength = length($Host); $HostSpaceLength = 61 - $HostLength; $CountLength = length("$Conns"); $CountSpaceLength = 12 - $CountLength; print "\n" ." " x $HostSpaceLength . $Host . " |" . " " x $CountSpaceLength . $Conns . ""; $ConnCount += $Conns; } $CountLength = length("$ConnCount"); $CountSpaceLength = 75 - $CountLength; print "\n" . "-" x 75; print "\n" . " " x $CountSpaceLength . "$ConnCount\n\n\n"; } if ( ( $Detail >= 5 ) and (keys %Connection)) { print "\n[IMAPd] Connections:". "\n=========================". "\n Host | Connections | SSL | Total ". "\n-------------------------------------- | ----------- | -------- | ---------"; my $ConnCount = 0; foreach my $Host (sort keys %Connection) { my $Total = $Connection{$Host}; if (defined ($ConnectionNonSSL{$Host})) { $Conns = $ConnectionNonSSL{$Host}; } else { $Conns = 0; } if (defined ($ConnectionSSL{$Host})) { $SSL = $ConnectionSSL{$Host}; } else { $SSL = 0; } $HostLength = length($Host); $HostSpaceLength = 38 - $HostLength; $CountLength = length("$Conns"); $CountSpaceLength = 12 - $CountLength; $SSLLength = length("$SSL"); $SSLSpaceLength = 9 - $SSLLength; $TotalLength = length("$Total"); $TotalSpaceLength = 10 - $TotalLength; print "\n" ." " x $HostSpaceLength . $Host . " |" . " " x $CountSpaceLength . $Conns . " |" . " " x $SSLSpaceLength . $SSL . " |" . " " x $TotalSpaceLength . $Total; $NonSSLCount += $Conns; $SSLCount += $SSL; $TotalCount += $Total; } $NonSSLLength = length("$NonSSLCount"); $NonSSLSpaceLength = 52 - $NonSSLLength; $SSLLength = length("$SSLCount"); $SSLSpaceLength = 9 - $SSLLength; $TotalLength = length("$TotalCount"); $totalSpaceLength = 10 - $TotalLength; print "\n" . "-" x 75; print "\n" . " " x $NonSSLSpaceLength . $NonSSLCount . " |" . " " x $SSLSpaceLength . $SSLCount . " |" . " " x $totalSpaceLength . $TotalCount . "\n\n\n"; } if (keys %Logout2) { print "\n[IMAPd] Logout stats:". "\n====================". "\n User | Logouts | Downloaded | Mbox Size". "\n--------------------------------------- | ------- | ---------- | ----------"; my $ConnCount = 0; my $SizeAll = 0; my $DownAll = 0; foreach my $User (sort keys %Logout2) { $Conns = $Logout2{$User}; $UserLength = length($User); $UserSpaceLength = 39 - $UserLength; $CountLength = length("$Conns"); $CountSpaceLength = 8 - $CountLength; $Down = $DownloadedMessagesSize{$User}; if (! defined $Down) { $Down = 0; #Hack } $DownSpaceLength = 11 - length($Down); #$Size = $MboxSize{$User}; $Size = 0; #Hack $SizeSpaceLength = 11 - length($Size); print "\n" ." " x $UserSpaceLength . $User . " |" . " " x $CountSpaceLength . $Conns . " |" . " " x $DownSpaceLength . $Down . " |" . " " x $SizeSpaceLength . $Size; $ConnCount += $Conns; $SizeAll += $Size; $DownAll += $Down; } $CountLength = length("$ConnCount"); $CountSpaceLength = 49 - $CountLength; $DownLength = length($DownAll); $DownSpaceLength = 11 - $DownLength; $SizeLength = length($SizeAll); $SizeSpaceLength = 11 - $SizeLength; print "\n" . "-" x 75; print "\n" . " " x $CountSpaceLength . "$ConnCount" . " |" . " " x $DownSpaceLength . $DownAll . " |" . " " x $SizeSpaceLength . $SizeAll . "\n\n\n"; } if ( ( $Detail >= 10 ) and (keys %Login)) { print "\n[IMAPd] Successful Logins:\n"; my $LoginCount = 0; foreach my $User (sort keys %Login) { print " User $User: \n"; my $UserCount = 0; foreach my $Host (keys %{$Login{$User}}) { $HostCount = $Login{$User}{$Host}; print " From $Host: $HostCount Time(s)\n"; $UserCount += $HostCount; } $LoginCount += $UserCount; print " Total $UserCount Time(s)\n"; print "\n"; } print "Total $LoginCount successful logins\n\n"; } if ( ( $Detail >= 10 ) and (keys %AutoLogout)) { print "\nAutologout:\n"; foreach my $User (sort {$a cmp $b} keys %AutoLogout) { print " $User:\n"; foreach my $Host (sort {$a cmp $b} keys %{$AutoLogout{$User}}) { print " $Host: $AutoLogout{$User}{$Host} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %KilledSession)) { print "\nKilled IMAP sessions:\n"; foreach my $User (sort {$a cmp $b} keys %KilledSession) { print " $User:\n"; foreach my $Reason (sort {$a cmp $b} keys %{$KilledSession{$User}}) { print " $Reason: $KilledSession{$User}{$Reason} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %SocketErrors)) { print "\nSocket Errors in connections with:\n"; foreach my $Host (sort {$a cmp $b} keys %SocketErrors) { print " $Host: $SocketErrors{$Host} Time(s)\n"; } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/courier0000644000175000017500000005607715075123620020404 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ### Copyright 2003-2006 by Willi Mann ### Covered under the included MIT/X-Consortium License: ### http://www.opensource.org/licenses/mit-license.php ### All modifications and contributions by other persons to ### this script are assumed to have been donated to the ### Logwatch project and thus assume the above copyright ### and licensing terms. If you want to make contributions ### under your own copyright or a different license this ### must be explicitly stated in the contribution an the ### Logwatch project reserves the right to not accept such ### contributions. If you have made significant ### contributions to this script and want to claim ### copyright please contact logwatch-devel@lists.sourceforge.net. ####################################################### ####################################################### # # Please don't change the formatting: # # if (...) { # ... # } # # but # # (while|foreach) .. # { # ... # } # ####################################################### use strict; #Could be necessary in some environments unless ($ENV{'courier_enable'} == 1) {exit 0}; my $Debug = $ENV{'LOGWATCH_DEBUG'}; my $DoLookup = $ENV{'courier_ip_lookup'}; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'}; my $overrideDetail = $ENV{'courier_override_detail_level'}; my $printAllUnmatched = $ENV{'courier_print_all_unmatched'}; $Detail = $overrideDetail if defined $overrideDetail; my $PrintMailQueue = $ENV{'courier_printmailqueue'}; my $Tables = $ENV{'courier_tables'}; my $RemoveAdditionalInfo = $ENV{'courier_removeadditionalinfo'}; my $MostFrequentSender = $ENV{'courier_mostfrequentsender'}; my $DeliverMailSize = 0; my $LastLine; my %OtherList; sub LookupIP { my ($name, $a1, $a2,$a3,$a4,$PackedAddr,$Addr); $Addr = $_[0]; ($a1,$a2,$a3,$a4) = split /\./,$Addr; $PackedAddr = pack('C4',$a1,$a2,$a3,$a4); if ($DoLookup) { if ($name = gethostbyaddr ($PackedAddr,2)) { return ($name . " (" . $Addr . ")"); } else { return ($Addr); } } else { return ($Addr); } } #Make pseudo IPv6 to IPv4 sub LookupIPv46 { my $IPv4Addr; my $Addr = shift; if ( ($IPv4Addr) = ($Addr =~ /::ffff:([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/ ) ) { return $IPv4Addr; } else { return $Addr; } } sub PushUnmatched { my $service = shift; my $line = shift; if ( $printAllUnmatched or $service =~ /courier/ or $service =~ /^authdaemon/ ) { $OtherList{$service}{$line}++; } } sub recprint1 { my $hash = shift; my $depth = shift; my $prefar = shift; my $IPar = shift; my $indention = shift || 0; my $calldepth = shift || $depth; my $sizehash = shift; my $out = ""; my $inum = 0; my $size = 0; my @prefar = @{$prefar} if defined $prefar; my $pref = shift(@prefar); $pref .= " " if defined $pref; $pref = "" if not defined $pref; my @IPar = @{$IPar} if defined $IPar; my $IP = shift(@IPar); foreach my $key1 (sort keys %{$hash}) { my ( $rout, $nmb, $rsize); if ($depth > 1) { ($rout, $nmb, $rsize) = recprint1($hash->{$key1}, $depth - 1, \@prefar, \@IPar, $indention + 3, $calldepth, defined $sizehash ? $sizehash->{$key1} : undef) if $depth > 1; } else { $rout = ""; $nmb = $hash->{$key1}; $rsize = $sizehash->{$key1} if defined $sizehash; } if ($key1 ne "-" or $rout ne "") { $out .= (" " x $indention).$pref; if ($IP) { $out .= LookupIP(LookupIPv46($key1)); } else { $out .= $key1; } $out .= " - ".nTimes($nmb); $out .= ", ".$rsize." Bytes" if defined $sizehash; $out .= "\n"; $out .= $rout; } $out .= "\n" if $depth == $calldepth; $inum += $nmb; $size += $rsize; } if (wantarray) { return ($out, $inum, $size); } else { return $out; } } sub tblprint1 { my $hash = shift; my $sizehash = shift; my $onerec = shift || 0; #boolean my $IP = shift || 0; #boolean my $onerectitle = shift || ""; my $tablecolumns = shift; #LISTREF my $tabletitle = shift || ""; if (not $onerec){ return maketbl1($hash, $sizehash, $IP, $tablecolumns, $tabletitle); } else { my ( $out, $nmb, $size ) = ("", 0,0); my ( $lout, $lnmb, $lsize); foreach my $key1 (sort keys %{$hash}) { ($lout, $lnmb, $lsize) = maketbl1($hash->{$key1}, defined $sizehash ? $sizehash->{$key1} : undef, $IP, $tablecolumns, undef, "-"); if ($onerectitle) { $out .= "$onerectitle: $key1\n"; $out .= "-" x (2 + length($onerectitle) + length($key1))."\n"; } else { $out .= "$key1\n"; $out .= "-" x (length($key1))."\n"; } $out .= $lout; $nmb += $lnmb; $size += $lsize; } if ($tabletitle) { my $tout = $tabletitle; $tout .= ", ".nTimes($nmb); $tout .= ", ".$size." Bytes" if defined $sizehash; $tout .= "\n"."=" x length($tabletitle)."\n\n"; $out = $tout.$out; } return ($out, $nmb, $size); } } sub max { my $ret = shift; foreach my $val (@_) { $ret = $val if $val > $ret; } return $ret; } sub maketbl1 { my $hash = shift; my $sizehash = shift; my $IP = shift || 0; #boolean my $tablecolumns = shift; #LISTREF my $tabletitle = shift; my $uchar = shift || "="; my @columnmax; my $out = ""; my $inmb = 0; my $size = 0; foreach my $column (@{$tablecolumns}) { push @columnmax, length("$column"); } # Get the max length for column 1, ("main") # column 2, ("number") # column 3, ("size") # TODO: Enhance; max can take any number of arguments, so make use of that # (maybe it turns out to be a bad idea) my %IPhash; foreach my $key1 (keys %{$hash}) { if ($IP) { $IPhash{$key1} = LookupIP(LookupIPv46($key1)); $columnmax[0] = max ($columnmax[0], length($IPhash{$key1})); } else { $columnmax[0] = max ($columnmax[0], length("$key1")); } $columnmax[1] = max ($columnmax[1], length($hash->{$key1})); $inmb += $hash->{$key1}; $columnmax[2] = max ($columnmax[2], length($sizehash->{$key1})) if defined $sizehash; $size += $sizehash->{$key1} if defined $sizehash; } #for last line $columnmax[1] = max $columnmax[1], length $inmb; $columnmax[2] = max $columnmax[2], length $size; if (defined $tabletitle) { $out .= "$tabletitle\n"; $out .= $uchar x length($tabletitle)."\n"; } #first line $out .= " " x ($columnmax[0] - length($tablecolumns->[0])).$tablecolumns->[0]; $out .= " | "; $out .= " " x ($columnmax[1] - length($tablecolumns->[1])).$tablecolumns->[1]; $out .= " | " if defined $sizehash; $out .= " " x ($columnmax[2] - length($tablecolumns->[2])).$tablecolumns->[2] if defined $sizehash; $out .= "\n"; #second line $out .= "-" x $columnmax[0]. " | ". "-" x $columnmax[1]; $out .= " | ". "-" x $columnmax[2] if defined $sizehash; $out .= "\n"; #tablebody foreach my $key1 (sort keys %{$hash}) { my $nmb = $hash->{$key1}; #col1 #whitespace if ($IP) { $out .= " " x ($columnmax[0] - length($IPhash{$key1})); $out .= $IPhash{$key1}; } else { $out .= " " x ($columnmax[0] - length($key1)); $out .= $key1; } $out .= " | "; #col2 $out .= " " x ($columnmax[1] - length($nmb)). $hash->{$key1}; if (defined $sizehash) { $out .= " | ". " " x ($columnmax[2] - length($sizehash->{$key1})).$sizehash->{$key1}; } $out .= "\n"; } #second line (copied from above, if someone has a better idea, tell me.) $out .= "-" x $columnmax[0]. " | ". "-" x $columnmax[1]; $out .= " | ". "-" x $columnmax[2] if defined $sizehash; $out .= "\n"; #last line $out .= " " x ($columnmax[0] + 3 + $columnmax[1] - length($inmb)). $inmb; $out .= " " x ($columnmax[2] + 3 - length($size) ). $size if defined $sizehash; $out .= "\n\n"; return ($out, $inmb, $size); } sub MakeTblReason { my $OrigReason = shift; my $TblReason; if ( (!(( $TblReason) = ( $OrigReason =~ /^(".*?").*/ )) and (!(( $TblReason) = ( $OrigReason =~ /^(.*?): .*/ )))) or !($RemoveAdditionalInfo) ) { $TblReason = $OrigReason; } return $TblReason; } sub nTimes { my $nmb = shift; if ($nmb == 1) { return "1 Time"; } else { return "$nmb Times"; } } if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside Courier Filter \n\n"; } #List vars here to avoid case-sensitive typos my $AuthdRestart; my $BrokenPipe; my %Connection; my %ConnTimeout; my $ConResetBP; my %Deferred; my %DeSu; my %DeSuSz; my %DeSuTbl; my %DeSuTblSz; my %DfrdTbl; my %ErrorMsgs; my %ErrorTbl; my %ErrorTbl2; my %Failed; my %FailRe; my $From; my $Host; my $ID; my $LastSMTPErrCode; my %Login; my %LoginFailed; my %Logout; my %Logout2; my %LogoutSize; my %LogoutSize2; my $RespawnCourier = 0; my $ShutdownCourier; my $Size; my $SSLstop; my $StartCourier = 0; my $Startpfilter; my $StopCourier = 0; my $Stoppfilter; my $ThisLine; my $To; my $User; my $service; while (defined($ThisLine = )) { chomp $ThisLine; my $Size2 = 0; my $Size = 0; #TODO: Make this more accurate (expand to \d+ times) if ( $ThisLine =~ /^... .. ..:..:.. \S+ last message repeated \d+ times/ ) { $ThisLine = $LastLine; } else { if (not (($service ) = ( $ThisLine =~ /^... .. ..:..:.. \S+ ([^\s:\[\]]+)(?:\[[0-9]+\]|): / ))) { next; } $ThisLine = $'; #$POSTMATCH } if ( ($ThisLine =~ /^Initializing */) or ($ThisLine =~ /^Installing */) or ($ThisLine =~ /^Installed: */) or ($ThisLine =~ /^Installation complete: / ) or ($ThisLine =~ /^stopping authdaemond children/ ) or ($ThisLine =~ /^Started .\/courier.*, pid=.*, maxdels=.*, maxhost=.*, maxrcpt=.*1/ ) or ($ThisLine =~ /^Waiting\. shutdown time=.*, wakeup time=.*, queuedelivering=.*, inprogress=.*/) or ($ThisLine =~ /^Loading STATIC transport module libraries./) or ($ThisLine =~ /^Purging /) or ($ThisLine =~ /^completed,id=/) or ($ThisLine =~ /^queuelo=.*, queuehi=.*/) or # Do we really want to ignore these? # currently i'm too lazy to include this ($ThisLine =~ /^started,ip=.*/) or ##courieresmtpd # example line: # id=00081D7A.3E9E0C51.000037A4,from=,addr=,size=53223,status: success: 1 Time(s) ($ThisLine =~ /id=.*?,from=<.*?>,addr=<.*?>,size=[0-9]*,status:.*/) ##courieresmtp ) { # Don't care about these... } # ESMTP, including all delivery elsif ( $service =~ /^(courierd|courieresmtpd|courieresmtp|courierlocal|courieruucp|courierfax|courierdsn)$/ ){ #First the don't cares if ( $ThisLine =~ /^newmsg,id=/ ) { } elsif ( $ThisLine =~ /started,id=.*?,from=<(.*?)>,module=(.*?),host=(.*?),addr=<(.*?)>/ ) { #Now starting, restarting ... } elsif ( $ThisLine =~ /^Courier .* Copyright/ ) { $StartCourier++; } elsif ( $ThisLine =~ /^SHUTDOWN: respawnlo limit reached/ ) { $RespawnCourier++; } elsif ( $ThisLine =~ /^SHUTDOWN: Stopping.../ ) { $StopCourier++; #Now it's getting interesting } elsif ( ($Host) = ( $ThisLine =~ /^error,relay=([^,]*?),/ ) ) { ##courieresmtpd # example lines: # error,relay=::ffff:209.214.170.188,from=,to=: 513 Relaying denied. # error,relay=::ffff:218.70.112.124,from=: 517 Invalid domain, see # error,relay=::ffff:62.67.54.144,msg="502 ESMTP command error",cmd: DATA $ThisLine = $'; my ( $From ,$To, $Msg ) = (undef, undef, undef); if ( $ThisLine =~ /^ident=[^,]*,/ ) { $ThisLine = $'; } if ( ($From) = ( $ThisLine =~ /^from=<([^<>]*?)>(:?,|)/ )) { $ThisLine = $'; } if (( $To ) = ( $ThisLine =~ /^to=<([^<>]*?)>/ ) ) { $ThisLine = $'; } if (( $Msg ) = ( $ThisLine =~ /^msg=(.*)/ )) { } $ThisLine =~ s/^: //; #Extract it my ($SMTPErrCode) = ($ThisLine =~ /^([0-9]{3})/); # next if already seen if($ThisLine =~ /^[0-9]{3} / and $LastSMTPErrCode == $SMTPErrCode) { $LastSMTPErrCode = 0; next; } #next if already seen but not last line. next if $LastSMTPErrCode == $SMTPErrCode and not defined $Msg; my $Reason = $ThisLine; $Reason = $Msg if defined $Msg; $ErrorMsgs{$Reason}{$Host}{$From || "-"}{$To || "-"}++ if not $Tables; my $TblReason = MakeTblReason($Reason) if $Tables; $ErrorTbl{$TblReason}{$Host}++ if $Tables; $LastSMTPErrCode = $SMTPErrCode; #zero it if done $LastSMTPErrCode = 0 if $ThisLine =~ /^[0-9]{3} / or defined $Msg; } elsif ( ($From, $To, $Size) = ( $ThisLine =~ /^id=.*?,from=<(.*?)>,addr=<(.*?)>,size=([0-9]*),success: .*/ ) ) { ##courieresmtp, courierlocal #example line: #id=00081D7A.3E9E0B39.000036E4,from=,addr=,size=35861,success: delivered: ff.ff.at [111.111.111.111] #DeliverSuccess = DeSu !!!!!!!! $DeSu{$From}{$To}++; $DeSuSz{$From}{$To} += $Size; $DeSuTbl{$To}++; $DeSuTblSz{$To} += $Size; } elsif ( ($ID, $From, $To, my $status) = ( $ThisLine =~ /^id=(.*),from=<(.*?)>,addr=<(.*?)>,status: (deferred|failure)/ ) ) { ##courieresmtp #example lines: deferred, failed delivery attempts #id=00081D03.3E850D34.000076BD,from=,addr=,status: deferred #id=00081D7B.3E9167E7.00002B27,from=,addr=,status: failure my $Reason = $FailRe{$ID}{$From}{$To}; if ($Reason eq "") { $Reason = "-"; } my $TblReason = MakeTblReason($Reason); if ( $status =~ /deferred/ ){ $Deferred{$Reason}{$From}{$To}++; $DfrdTbl{$TblReason}{$To}++; } else { #failure $Failed{$Reason}{$From}{$To}++; $ErrorTbl2{$TblReason}{$To}++; } } elsif ( ($ID, $From, $To, my $Reason) = ( $ThisLine =~ /^id=(.*?),from=<(.*?)>,addr=<(.*?)>:(.*)/ ) ) { ##courierd, courieresmtp #example line: #id=00079ED0.3E8A45E7.000042AF,from=,addr=: Connection timed out #id=00079ED0.3E975385.00005B66,from=,addr=: DNS lookup failed. #This is for the following lines to have the reason for failed or deferred. $FailRe{$ID}{$From}{$To} = $Reason; } elsif ( $ThisLine =~ /^Unexpected SSL connection shutdown./ ) { $SSLstop++; } elsif ( $ThisLine =~ /^writev: Broken pipe/ ) { $BrokenPipe++; } elsif ( $ThisLine =~ /^writev: Connection reset by peer/ ) { $ConResetBP++; } elsif ( ( $Host ) = ( $ThisLine =~ /^\[([^\]]*)\]: Connection timed out/ )) { $ConnTimeout{$Host}++; } else { PushUnmatched $service, $ThisLine; } } elsif ( $service =~ /^authdaemond/ ) { if ( $ThisLine =~ /^modules="[^"]*", daemons=\d*$/ ) { # Ignore } elsif ( $ThisLine =~ /^restarting authdaemond children/ ) { $AuthdRestart++; } else { PushUnmatched $service, $ThisLine; } } elsif ( $service =~ /^submit$/ ) { if ( $ThisLine =~ /^Broken pipe/ ) { $BrokenPipe++; } elsif ( $ThisLine =~ /^Connection reset by peer/ ) { $ConResetBP++; } else { PushUnmatched $service, $ThisLine; } } elsif ( $service =~ /^courierfilter$/ ) { if ( $ThisLine =~ /^Starting perlfilter/ ) { $Startpfilter++; } elsif ( $ThisLine =~ /^Stopping perlfilter/ ) { $Stoppfilter++; } else { PushUnmatched $service, $ThisLine; } } elsif ( $service =~ /^(:?pop3login|imaplogin|courierpop3login|pop3d|pop3d-ssl|imapd|imapd-ssl)$/ ) { my $proto = $service; $proto =~ s/.*pop.*/POP3/i; $proto =~ s/.*imap.*/IMAP/i; if ( ($Host) = ( $ThisLine =~ /^Connection, ip=\[(.*?)\]/ ) ) { ##pop3login, imaplogin, courierpop3login, pop3d, imapd #example line pop3, imapd?? #Connection, ip=[::ffff:192.168.0.24] $Connection{$proto}{$Host}++; } elsif ( ($User, $Host) = ( $ThisLine =~ /^LOGIN, user=(.*?), ip=\[(.*?)\]/ ) ) { ##pop3login, imaplogin, courierpop3login, pop3d, imapd #example line #LOGIN, user=xy, ip=[::ffff:192.168.0.12] $Login{$proto}{$User}{$Host}++; } elsif ( ( ( $User, $Host, $Size) = ( $ThisLine =~ /^LOGOUT, user=(.*?), ip=\[(.*?)\], (?:port=\[\d+\], )?(?:top|headers)=[0-9]*, (?:retr|body)=([0-9]*)/ ) ) || ( ( $User, $Host, $Size, $Size2) = ( $ThisLine =~ /^DISCONNECTED, user=(.*?), ip=\[(.*?)\], headers=([0-9]*?), body=([0-9]*)/ ) ) ) { ###pop3login, imaplogin, courierpop3login, pop3d, imapd #example line #LOGOUT, user=xy, ip=[::ffff:192.168.0.24], top=0, retr=0 #DISCONNECTED, user=zz@uu.ch, ip=[::ffff:192.168.0.1], headers=0, body=1100 $Size += $Size2 if defined $Size2; $Logout{$proto}{$User}{$Host}++; $Logout2{$proto}{$User}++; $LogoutSize{$proto}{$User}{$Host} += $Size; $LogoutSize2{$proto}{$User} += $Size; } elsif ( ($Host) = ( $ThisLine =~ /^LOGIN FAILED, ip=\[(.*?)\]/ ) ) { ## pop3login, imaplogin, courierpop3login, pop3d, imapd #example line #LOGIN FAILED, ip=[::ffff:192.168.200.199] $LoginFailed{$proto}{$Host}++; } else { PushUnmatched $service, $ThisLine; } } else { # Report any unmatched entries... PushUnmatched $service, $ThisLine; } $LastLine = $ThisLine; } if ( ( $Detail >= 5 ) and ($PrintMailQueue ) ) { print "\n\n\nCurrent State of the Mail Queue:\n". "================================\n\n"; my $MailqPath = `which mailq` || "/usr/bin/mailq"; chomp $MailqPath; if (-x $MailqPath) { print `$MailqPath`; print "\n\n"; } } #StartCourier ... if ( $Detail >= 5 ) { my $SelfRestart = $RespawnCourier; print "Courier restarted itself ".nTimes($SelfRestart)."\n" if $SelfRestart; my $HandStart = $StartCourier - $SelfRestart; print "Courier was started by hand (or init) ".nTimes($HandStart)."\n" if $HandStart; my $HandStop = $StopCourier; print "Courier was stopped by hand (or init) ".nTimes($HandStop)."\n" if $HandStop; print "The authdaemon was restarted ".nTimes($AuthdRestart)."\n" if $AuthdRestart; print "\n" if $RespawnCourier + $StartCourier + $StopCourier > 0; } print "Unexpected SSL connection shutdowns: ".nTimes($SSLstop)."\n" if $SSLstop; print "Broken Pipes: ".nTimes($BrokenPipe)."\n" if $BrokenPipe; print "Connection Reset by Peer: ".nTimes($ConResetBP)."\n" if $ConResetBP; if ( keys %ConnTimeout ) { my ($out, $nmb) = recprint1(\%ConnTimeout, 1, [ "Host" ], [1], 2, 2); print "\nConnections timed out: ".nTimes($nmb)."\n"; print $out."\n"; } if ( ( $Detail >= 5 ) and (keys %Connection) and (!$Tables)) { my ($out, $nmb) = recprint1(\%Connection, 2, [ "Protocol", "Host" ], [0,1], 2); print "\nConnections: ".nTimes($nmb)."\n"; print $out."\n"; } if ( ( $Detail >= 5 ) and (keys %Connection) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%Connection, undef, 1, 1, "Connections", [ "Host", "#" ], ""); print $out; } if ( ( $Detail >= 0 ) and (keys %LoginFailed) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%LoginFailed, undef, 1, 1, "Login Failures", [ "Host", "#" ], ""); print "\n".$out; } if ( ( $Detail >= 0 ) and (keys %LoginFailed) and (!$Tables)) { my ($out, $nmb) = recprint1(\%LoginFailed, 2, [ "Protocol", "Host" ], [0,1], 2); print "\nLogin Failed: ".nTimes($nmb)."\n"; print $out."\n"; } if ( ( $Detail >= 5 ) and (keys %Logout2) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%Logout2, \%LogoutSize2, 1, 1, "Logins", [ "Host", "#", "Size" ], ""); print $out; } if ( ( $Detail >= 5 ) and (keys %Logout) and (!$Tables)) { my ($out, $nmb) = recprint1(\%Logout, 3, [ "Protocol", "User", "Host" ], [0,0,1], 2, undef, \%LogoutSize); print "\nLogins: ".nTimes($nmb)."\n"; print $out."\n"; } #Fixme why have login and logout has print 2 login reports -mgt if ( ( $Detail >= 10 ) and (keys %Login) and (!$Tables)) { my ($out, $nmb) = recprint1(\%Login, 3, [ "Protocol", "User", "Host" ], [0,0,1], 2, undef); print "\nSuccessful Logins: ".nTimes($nmb)."\n"; print $out."\n"; } if ( ( $Detail >= 0 ) and (keys %ErrorTbl) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%ErrorTbl, undef, 1, 1, "", [ "Host", "#" ], "Errors in remote to local connections"); print $out; } if ( ( $Detail >= 0 ) and (keys %ErrorMsgs) and (!$Tables) ) { my ($out, $nmb) = recprint1 ( \%ErrorMsgs, 4, [ "because", "Host", "From", "To" ], [0,1,0,0], 2); print "\nErrors in remote to local connections: ".nTimes($nmb)."\n\n"; print "$out\n"; } if ( ( $Detail >= 0 ) and (keys %DfrdTbl) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%DfrdTbl, undef, 1, 0, "", [ "Recipient", "#" ], "Deferred delivery attempts"); print $out; } if ( ( $Detail >= 0 ) and (keys %Deferred) and (!$Tables)) { my ($out, $nmb) = recprint1(\%Deferred, 3, [ "because", "From", "To" ], undef, 2,4); print "\nDeferred delivery attempts: ".nTimes($nmb)."\n"; print $out; } if ( ( $Detail >= 0 ) and (keys %ErrorTbl2) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%ErrorTbl2, undef, 1, 0, "", [ "Recipient", "#" ], "Failed delivery attempts"); print $out; } if ( ( $Detail >= 0 ) and (keys %Failed) and (!$Tables)) { my ($out, $nmb) = recprint1(\%Failed, 3, [ "because", "From", "To" ], undef, 2); print "\nFailed delivery attempts: ".nTimes($nmb)."\n\n"; print "$out\n"; } if ( ( $Detail >= 5 ) and (keys %DeSu) and (!$Tables) ) { my ($out, $nmb, $size) = recprint1 ( \%DeSu, 2, [ "From", "To" ], [0,0], 2, undef, \%DeSuSz); print "\n\nSuccessful deliveries: ".nTimes($nmb).", $size Bytes\n\n"; print "$out\n"; } if ( ( $Detail >= 5 ) and (keys %DeSuTbl) and ($Tables)) { my ( $out, $nmb, $size) = tblprint1(\%DeSuTbl, \%DeSuTblSz, 0, 0, "", [ "Recipient", "#", "Size" ], "Successful deliveries"); print $out; } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; my $out = recprint1( \%OtherList, 2, undef,undef, 2); print "$out\n"; } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/named0000644000175000017500000013314715075123620020012 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ##################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':ip'; use strict; DoLookup( ValueOrDefault($ENV{'named_ip_lookup'}, 0) ); my $Debug = ValueOrDefault($ENV{'LOGWATCH_DEBUG'}, 0); my $Detail = ValueOrDefault($ENV{'LOGWATCH_DETAIL_LEVEL'}, 0); my $FailedZoneTransferThreshold = $ENV{'failed_zone_transfer_threshold'} || 0; my $IgnoreDeniedZoneTransfers = $ENV{'ignore_denied_zone_transfers'} || 0; #Init Counters my $DebugCounter= my $StartNamed= my $ReloadNamed= my $ShutdownNamed= my $ShutdownNamedFail= my $TCPClientQuota= 0; #Init String Containers my ( $Address, $Host, $Zone, $Addr, $CCC, $Entry, $Error, $File, $From, $Hint, $Ldom, $Line, $Log, $Name, $Net, $Problem, $Reason, $Response, $Rhost, $Server, $Channel, $Client, $ErrorText, $Message, $RR, $Rcode, $Source, $ViewName, $Way, $Master, ); #Init Arrays my @CNAMEAndOther = (); #Init Hashes my ( %AXFR, %BadZone, %ChannelAddFail, %ConnectionRefused, %DeferredZoneTransfers, %DeniedQuery, %DeniedQueryNoCache, %DeniedTCPClient, %DeniedZoneTransfers, %FailedZoneTransfers, %FormErr, %InsecUpdate, %JournalFail, %LameServer, %MasterFailure, %NetworkUnreachable, %NonAuthoritative, %RetryLimit, %UnexpRCODE, %UpdateDenied, %UpdateForwardingDenied, %ZoneExpired, %ZoneFileErrors, %ZoneLoaded, %ZoneReceivedNotify, %ZoneRefusedNotify, %ZoneRemoved, %ZoneUpdates, %Limit, %LimitDrop, %CCCommands, %CCMessages, %CCMessages2, %ConfProb, %DNSSECBadCache, %DNSSECError, %DNSSECInsec, %DNSSECInvalid, %ErrOpenFiles, %GeoIPError, %HUR, %Hints, %LimitSlip, %NError, %NUR, %NoSOA, %OtherList, %StartLog, %UnknownCCCommands, %BadCookie, %Timeout, %LoopDetected, %MissingCookie, %FormatError, %Rewrites, %PolicyZoneChanged, %JournalRemoved, %ZoneNotLoaded, ); # Avoid "Use of uninitialized value" warning messages. sub ValueOrDefault { my ($value, $default) = @_; return ($value ? $value : $default); } if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside NAMED Filter \n\n"; $DebugCounter = 1; } while (defined(my $ThisLine = )) { if ( $Debug >= 30 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } if ( ($ThisLine =~ /RR negative cache entry/) or ($ThisLine =~ /ns_....: .* NS points to CNAME/) or ($ThisLine =~ /accept: connection reset by peer/) or ($ThisLine =~ /Connection reset by peer/) or # typo fixed in 2004 release ($ThisLine =~ /transfer(r)?ed serial/) or ($ThisLine =~ /There may be a name server already running/) or ($ThisLine =~ /exiting/) or ($ThisLine =~ /running/) or ($ThisLine =~ /NSTATS /) or ($ThisLine =~ /Cleaned cache of \d+ RRs/) or ($ThisLine =~ /max-cache-size .* setting to /) or ($ThisLine =~ /USAGE \d+ \d+ CPU=\d+.*/) or ($ThisLine =~ /XSTATS /) or ($ThisLine =~ /Ready to answer queries/) or ($ThisLine =~ /Forwarding source address is/) or ($ThisLine =~ /bad referral/) or ($ThisLine =~ /prerequisite not satisfied/) or ($ThisLine =~ /(rcvd|Sent) NOTIFY/) or ($ThisLine =~ /ns_resp: TCP truncated/) or ($ThisLine =~ /No possible A RRs/) or ($ThisLine =~ /points to a CNAME/) or ($ThisLine =~ /dangling CNAME pointer/) or ($ThisLine =~ /listening on/) or ($ThisLine =~ /unrelated additional info/) or ($ThisLine =~ /Response from unexpected source/) or ($ThisLine =~ /No root nameservers for class IN/) or ($ThisLine =~ /recvfrom: No route to host/) or # Be sure to catch: transfer of 'zone' from IP#53: failed to connect: timed out # not exact just triggers a full transfer ($ThisLine =~ /transfer of .*: (:?IXFR|AXFR(:?|-style IXFR) (:?started|ended)|connected using|failed while receiving responses: not exact)/) or # This will generate another error that we will catch ($ThisLine =~ /transfer of .*: resetting/) or ($ThisLine =~ /Transfer status: (:?success|up to date)/) or ($ThisLine =~ /using \d+ CPU/) or ($ThisLine =~ /loading configuration/) or ($ThisLine =~ /command channel listening/) or ($ThisLine =~ /configuring command channel from/) or ($ThisLine =~ /interface ignored/) or ($ThisLine =~ /no IPv6 interfaces found/) or ($ThisLine =~ /IPv6 socket API is incomplete; explicitly binding to each IPv6 address separately/) or ($ThisLine =~ /using \d+ UDP listeners? per interface/) or ($ThisLine =~ /^running/) or ($ThisLine =~ /^exiting/) or ($ThisLine =~ /no longer listening/) or ($ThisLine =~ /the default for the .* option is now/) or ($ThisLine =~ /stopping command channel on \S+/) or ($ThisLine =~ /Malformed response from/) or ($ThisLine =~ /client .* response from Internet for .*/) or # ($ThisLine =~ /client .+ query \(cache\) '.*' denied/) or ($ThisLine =~ /client .+(?: \([^)]+\))?: query:/) or # Do we really want to ignore these? #($ThisLine =~ /unknown logging category/) or ($ThisLine =~ /could not open entropy source/) or ($ThisLine =~ /\/etc\/rndc.key: file not found/) or ($ThisLine =~ /sending notifies/) or # file syntax error get reported twice and are already caught below ($ThisLine =~ /loading master file/) or ($ThisLine =~ /^ succeeded$/) or ($ThisLine =~ /\*\*\* POKED TIMER \*\*\*/) or # The message about the end of transfer is the interesting one ($ThisLine =~ /: Transfer started./) or ($ThisLine =~ /D-BUS service (disabled|enabled)./) or ($ThisLine =~ /D-BUS dhcdbd subscription disabled./) or ($ThisLine =~ /automatic empty zone/) or ($ThisLine =~ /binding TCP socket: address in use/) or ($ThisLine =~ /dbus_mgr initialization failed. D-BUS service is disabled./) or ($ThisLine =~ /dbus_svc_add_filter failed/) or ($ThisLine =~ /isc_log_open 'named.run' failed: permission denied/) or ($ThisLine =~ /weak RSASHA1 \(5\) key found \(exponent=3\)/) or ($ThisLine =~ /Bad file descriptor/) or ($ThisLine =~ /open: .*: file not found/) or ($ThisLine =~ /queries: client [\.0-9a-fA-F#:]* view localhost_resolver: query: .* IN .*/) or ($ThisLine =~ /zone .*: NS '.*' is a CNAME \(illegal\)/) or ($ThisLine =~ /skipping nameserver '.*' because it is a CNAME,/) or ($ThisLine =~ /zone .*: zone serial unchanged. zone may fail to transfer to slaves/) or ($ThisLine =~ /zone .*: loading from master file .* failed/) or ($ThisLine =~ /zone .*: NS '.*' has no address records/) or ($ThisLine =~ /zone .* \(signed\): receive_secure_serial: unchanged/) or ($ThisLine =~ /.*: not a valid number$/) or ($ThisLine =~ /^(.*: )?unexpected end of input/) or ($ThisLine =~ /too many timeouts resolving '.*' .*: disabling EDNS/) or ($ThisLine =~ /too many timeouts resolving '.*' .*: reducing the advertised EDNS UDP packet size to .* octets/) or ($ThisLine =~ /reloading zones succeeded/) or ($ThisLine =~ /rpz: .*: reload (?:start|done)/) or ($ThisLine =~ /generating session key/) or ($ThisLine =~ /success resolving '.*' \(in '.*'?\) after disabling EDNS/) or ($ThisLine =~ /success resolving '.*' \(in '.*'?\) after disabling EDNS/) or ($ThisLine =~ /success resolving '.*' after disabling qname minimization due to 'failure'/) or ($ThisLine =~ /success resolving '.*' after disabling qname minimization due to 'ncache nxdomain'/) or ($ThisLine =~ /the working directory is not writable/) or ($ThisLine =~ /using default UDP\/IPv[46] port range: \[[0-9]*, [0-9]*\]/) or ($ThisLine =~ /adjusted limit on open files from [0-9]* to [0-9]*/) or ($ThisLine =~ /using up to [0-9]* sockets/) or ($ThisLine =~ /built with/) or ($ThisLine =~ /compiled (by|with)/) or ($ThisLine =~ /linked to/) or ($ThisLine =~ /threads support is /) or ($ThisLine =~ /TTL differs in rdataset, adjusting [0-9]* -> [0-9]*/) or ($ThisLine =~ /max open files \([0-9]*\) is smaller than max sockets \([0-9]*\)/) or ($ThisLine =~ /clients-per-query (?:de|in)creased to .*/) or ($ThisLine =~ /^must-be-secure resolving '.*': .*/) or ($ThisLine =~ /^(error \()?no valid (DS|KEY|RRSIG)\)? resolving '.*': .*/) or ($ThisLine =~ /^not insecure resolving '.*': .*/) or ($ThisLine =~ /^validating \@0x[[:xdigit:]]+: .* DS: must be secure failure/) or ($ThisLine =~ /^(error \()?broken trust chain\)? resolving '.*': .*/) or ($ThisLine =~ /dumping all zones, removing journal files: success/) or ($ThisLine =~ /journal file [^ ]* does not exist, creating it/) or ($ThisLine =~ /master .* deleted from unreachable cache/) or ($ThisLine =~ /serial number \(\d+\) received from master/) or ($ThisLine =~ /zone .*: skipping zone transfer as master .* is unreachable \(cached\)/) or ($ThisLine =~ /zone .*: notify from .*: serial \d+/) or ($ThisLine =~ /zone [^:]+: serial \d+/) or ($ThisLine =~ /zone is up to date/) or ($ThisLine =~ /refresh in progress, refresh check queued/) or ($ThisLine =~ /refresh: NODATA response from master/) or ($ThisLine =~ /update with no effect/) or ($ThisLine =~ /reading built-in trusted keys from file/) or ($ThisLine =~ /reading built-in trust anchors from file/) or ($ThisLine =~ /using built-in trusted-keys/) or ($ThisLine =~ /using built-in keys instead/) or ($ThisLine =~ /set up managed keys zone/) or ($ThisLine =~ /managed-keys-zone.*[Kk]ey .*now trusted/) or ($ThisLine =~ /forwarding update for zone/) or ($ThisLine =~ /forwarded dynamic update: master [^ ]* returned: (NXRRSET|YXDOMAIN)/) or ($ThisLine =~ /using .* as GeoIP directory/) or ($ThisLine =~ /GEO-.* Build/) or ($ThisLine =~ /initializing GeoIP /) or ($ThisLine =~ /looking for GeoIP2? databases in /) or ($ThisLine =~ /opened GeoIP2? database /) or # the following seems okay since it says "success" ($ThisLine =~ /managed-keys-zone.*: No DNSKEY RRSIGs found for '.*': success/) or # the following seems to happen on startup ($ThisLine =~ /managed-keys-zone.*: Unable to fetch DNSKEY set '.*': operation canceled/) or ($ThisLine =~ /managed-keys-zone.*: Unable to fetch DNSKEY set '.*': timed out/) or ($ThisLine =~ /^sizing zone task pool based on \d+ zones/) or ($ThisLine =~ /^BIND \d+ is maintained by Internet Systems Consortium/) or ($ThisLine =~ /a non-profit 501/) or ($ThisLine =~ /corporation. Support and training for BIND \d+ are/) or ($ThisLine =~ /available at https:\/\/www.isc.org\/support/) or ($ThisLine =~ /----------------------------------------------------/) or ($ThisLine =~ /^[^ ]+ algorithms: /) or ($ThisLine =~ /^TKEY mode \d+ support /) or ($ThisLine =~ /New key .* observed for zone/) or ($ThisLine =~ /next key event: /) or ($ThisLine =~ /reconfiguring zone keys/) or ($ThisLine =~ /using built-in DLV key/) or ($ThisLine =~ /trust-anchor-telemetry/) or ($ThisLine =~ /^adb: grow_entries /) or # ($ThisLine =~ /reading built-in trusted keys from file/) or ($ThisLine =~ /all zones loaded/) or ($ThisLine =~ /resolver priming query complete/) or ($ThisLine =~ /client .* signer .* approved/) or ($ThisLine =~ /stop limiting/) or # Previous line appears to contain the error ($ThisLine =~ /client .*: query failed .* for .* at /) or ($ThisLine =~ /increase from .* to .* RRL.* entries/) or ($ThisLine =~ /flushing caches in all views succeeded/) or ($ThisLine =~ /automatic interface scanning terminated/) or # ignore this line because the following line describes the error ($ThisLine =~ /unexpected error/) ) { # Don't care about these... } elsif ( ($ThisLine =~ /starting\..*named/) or ($ThisLine =~ /starting BIND/) or ($ThisLine =~ /named startup succeeded/) ) { $StartNamed++; } elsif ( $ThisLine =~ /(reloading nameserver|named reload succeeded)/ ) { $ReloadNamed++; } elsif ( ($ThisLine =~ /shutting down/) or ($ThisLine =~ /named shutting down/ ) or ($ThisLine =~ /named shutdown succeeded/ ) ) { $ShutdownNamed++; } elsif ( $ThisLine =~ /named shutdown failed/ ) { $ShutdownNamedFail++; } elsif ( $ThisLine =~ /TCP client quota reached/ ) { $TCPClientQuota++; } elsif ( (($Host, $Zone) = ( $ThisLine =~ /client ([^\#]+)#[^\:]+: (?:view \w+: )?\s*zone transfer '(.+)' denied/ )) or (($Host, $Zone) = ( $ThisLine =~ /client ([^\#]+)#[^\:]+: (?:view \w+: )?\s*bad zone transfer request: '(.+)':/ )) ) { $DeniedZoneTransfers{$Host}{$Zone}++ unless $IgnoreDeniedZoneTransfers; } elsif ( ($Zone) = ( $ThisLine =~ /zone (.+): not loaded due to errors/ ) ) { $ZoneNotLoaded{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /zone (.+) zone transfer deferred due to quota/ ) ) { $DeferredZoneTransfers{$Zone}++; } elsif ( ($Zone, $Host) = ( $ThisLine =~ /transfer of '(.+)' from ([^\#]+)#[^\:]+: (failed|(Transfer status|giving up): ((network|host) unreachable|timed out|connection refused|REFUSED))/ ) ) { $FailedZoneTransfers{$Host}{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /cache zone \"(.*)\" loaded/ ) ) { $ZoneLoaded{"cache $Zone"}++; } elsif ( ($Zone) = ( $ThisLine =~ /cache zone \"(.*)\" .* loaded/ ) ) { $ZoneLoaded{"cache $Zone"}++; } elsif ( ($Zone) = ( $ThisLine =~ /automatic empty zone: (.*)/ ) ) { $ZoneLoaded{"automatic empty zone $Zone"}++; } elsif ( ($Zone) = ( $ThisLine =~ /primary zone \"(.+)\" loaded/ ) ) { $ZoneLoaded{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /master zone \"(.+)\" .* loaded/ ) ) { $ZoneLoaded{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /secondary zone \"(.+)\" loaded/ ) ) { $ZoneLoaded{"secondary $Zone"}++; } elsif ( ($Zone) = ( $ThisLine =~ /slave zone \"(.+)\" .* loaded/ ) ) { $ZoneLoaded{"secondary $Zone"}++; } elsif ( ($Zone) = ( $ThisLine =~ /loading policy zone '(.+)' changed/ ) ) { $PolicyZoneChanged{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /zone (.+): expired/ ) ) { $ZoneExpired{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /zone (.+): loaded serial/ ) ) { $ZoneLoaded{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /(managed-keys-zone.*): loaded serial/ ) ) { $ZoneLoaded{$Zone}++; } elsif ( (undef,$Addr,$Server) = ( $ThisLine =~ /(C|c)onnection refused\)? resolving '(.+)': (.+)/ ) ) { $ConnectionRefused{$Addr}{$Server}++; } elsif ( (undef,$Addr,undef,$Server) = ( $ThisLine =~ /ame server (on|resolving) '(.+)' \(in .+\):\s+(\[.+\]\.\d+)?\s*'?(.+)'?:?/ ) ) { $LameServer{$Addr}{$Server}++; } elsif ( (($Zone) = ( $ThisLine =~ /Zone \"(.+)\" was removed/ )) or (($Zone) = ( $ThisLine =~ /zone (.+): \(.*\) removed/ )) ) { $ZoneRemoved{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /received notify for zone '(.*)'/ ) ) { $ZoneReceivedNotify{$Zone}++; } elsif ( ($Zone) = ( $ThisLine =~ /zone (.+): refused notify from non-(?:master|primary)/ ) ) { $ZoneRefusedNotify{$Zone}++; # } elsif ( ($Rhost,$Ldom,$Reason) = ( $ThisLine =~ /client ([\d\.a-fA-F:]+) bad zone transfer request: '(.+)': (.+)$/ ) ) { } elsif ( ($Rhost,$Ldom,$Reason) = ( $ThisLine =~ /client ([\.0-9a-fA-F:]+)#\d+: bad zone transfer request: '(.+)': (.+)/ ) ) { $BadZone{$Reason}{"$Rhost ($Ldom)"}++; } elsif ( ($Host) = ( $ThisLine =~ /([^ ]+) has CNAME and other data \(invalid\)/ ) ) { push @CNAMEAndOther, $Host; } elsif ( ($File,$Line,$Entry,$Error) = ( $ThisLine =~ /dns_master_load: ([^:]+):(\d+): ([^ ]+): (.+)$/ ) ) { $ZoneFileErrors{$File}{"$Entry: $Error"}++; } elsif ( ($File,$Line,$Entry,$Error) = ( $ThisLine =~ /warning: ([^:]+):(\d+): (.+)$/ ) ) { $ZoneFileErrors{$File}{"file does not end with newline: $Error"}++; } elsif ( ($Way,$Host) = ( $ThisLine =~ /([^ ]+): sendto\(\[([^ ]+)\].+\): Network is unreachable/ ) ) { my $FullHost = LookupIP ($Host); $NetworkUnreachable{$Way}{$FullHost}++; } elsif ( ($Host,$Way) = ( $ThisLine =~ /client (?:\@0x[0-9a-fA-F]+ )?(.*)#\d+(?: \(.*\))?: (?:view \w+: )?\s*error ([^ ]+) response: network unreachable/ ) ) { my $FullHost = LookupIP ($Host); $NetworkUnreachable{$Way}{$FullHost}++; } elsif ( ($Zone,$Message) = ( $ThisLine =~ /client [^\#]+#[^\:]+: (?:view \w+: )?\s*updating zone '([^\:]+)': (.*)$/ ) ) { $ZoneUpdates{$Zone}{$Message}++; } elsif ( ($Zone,$Source) = ( $ThisLine =~ /transfer of '([^\']+)' (from [^\#]+)#[^\:]+: Transfer completed: / ) ) { $ZoneUpdates{$Zone}{$Source}++; } elsif ( ($Host,$Zone) = ( $ThisLine =~ /approved AXFR from \[(.+)\]\..+ for \"(.+)\"/ ) ) { my $FullHost = LookupIP ($Host); $AXFR{$Zone}{$FullHost}++; } elsif ( ($Client) = ( $ThisLine =~ /warning: client (.*) no more TCP clients/ ) ) { my $FullClient = LookupIP ($Client); $DeniedTCPClient{$FullClient}++; } elsif ( ($Client) = ( $ThisLine =~ /client (?:\@0x[0-9a-fA-F]+ )?(.*)#\d+(?: \(.*\))?: (?:view \w+: )?\s*query \(cache\) (?:'.*' )?denied/ ) ) { my $FullClient = LookupIP ($Client); $DeniedQuery{$FullClient}++; } elsif ( ($Client) = ( $ThisLine =~ /client (?:\@0x[0-9a-fA-F]+ )?([^#]*)(#\d+)?(?: \(.*\))?: query '.*' denied/ ) ) { my $FullClient = LookupIP ($Client); $DeniedQueryNoCache{$FullClient}++; } elsif ( ($Entry) = ($ThisLine =~ / rewrite (\S+) /) ) { $Rewrites{$Entry}++; } elsif ( ($Rhost, $ViewName, $Ldom) = ($ThisLine =~ /client (?:\@0x[0-9a-fA-F]+ )?([\.0-9a-fA-F:]+)#\d+: (?:view (\w+): )?update '(.*)' denied/)) { $ViewName = ($ViewName ? "/$ViewName" : ""); $UpdateDenied{"$Rhost ($Ldom$ViewName)"}++; } elsif ( ($Rhost, $Reason) = ($ThisLine =~ /client (?:\@0x[0-9a-fA-F]+ )?([\.0-9a-fA-F:]+)#\d+: (.*)/)) { $UpdateDenied{"$Rhost ($Reason)"}++; } elsif ( ($Rhost, $Ldom) = ($ThisLine =~ /client ([\d\.]+)#\d+: update forwarding '(.*)' denied/)) { $UpdateForwardingDenied{"$Rhost ($Ldom)"}++; } elsif ( ($Zone, $Master, $Reason) = ($ThisLine =~ /zone (.*): forwarded dynamic update: master (.*)#\d+ returned: (.*)/)) { $UpdateForwardingDenied{"$Zone ($Master) $Reason"}++; } elsif ( ($Zone) = ($ThisLine =~ /zone '([0-9a-zA-Z.-]+)' allows updates by IP address, which is insecure/)) { $InsecUpdate{$Zone}++; } elsif ( ($Zone) = ($ThisLine =~ /zone ([0-9a-zA-Z.\/-]+): journal rollforward failed: journal out of sync with zone/)) { $JournalFail{$Zone}++; } elsif ( ($Zone) = ($ThisLine =~ /(managed-keys-zone.*): journal file is out of date: removing journal file/)) { $JournalRemoved{$Zone}++; } elsif ( ($Channel,$Reason) = ($ThisLine =~ /couldn't add command channel (.+#\d+): (.*)$/)) { $ChannelAddFail{$Channel}{$Reason}++; } elsif ( ($Zone,$Host,undef,$Reason) = ($ThisLine =~ /zone ([^ ]*): refresh: failure trying master ([^ ]*)#\d+( \(source .*\))?: (.*)/) ) { $MasterFailure{"$Zone from $Host"}{$Reason}++; } elsif ( ($Zone,$Reason,$Host) = ($ThisLine =~ /zone ([^ ]*): refresh: unexpected rcode \((.*)\) from master ([^ ]*)#\d+/) ) { $MasterFailure{"$Zone from $Host"}{$Reason}++; } elsif ( ($Zone) = ($ThisLine =~ /zone ([^\/]+)\/.+: refresh: non-authoritative answer from master/)) { $NonAuthoritative{$Zone}++; } elsif ( ($Zone) = ($ThisLine =~ /zone ([^\/]+)\/.+: refresh: retry limit for master \S+ exceeded/) ) { $RetryLimit{$Zone}++; } elsif ( ($Rcode, $Zone, $Host) = ($ThisLine =~ /(?:error \()?unexpected RCODE\)? \(?(.*?)\)? resolving '(.*)': (.*)$/) ){ # Remove port number, for proper IP sorting $Host =~ s/#.*//; $UnexpRCODE{$Rcode}{$Zone}{$Host}++; } elsif ( ($Rcode, $Zone, $Host) = ($ThisLine =~ /(.*) unexpected RCODE resolving '(.*)': (.*)$/) ){ # Remove port number, for proper IP sorting $Host =~ s/#.*//; $UnexpRCODE{$Rcode}{$Zone}{$Host}++; } elsif ( ($Problem,$Addr,$Server) = ($ThisLine =~ /((?:error \()??FORMERR\)? resolving) '([^ \/]+)(?:\/[^ ]+)?': ([.0-9a-fA-F:]+)(?:#\d+)?/) ) { $FormatError{$Problem}{$Addr}{$Server}++; } elsif ($ThisLine =~ /(?:error \()?FORMERR\)? resolving .*/) { chomp($ThisLine); $FormErr{$ThisLine}++; } elsif ( ($Server,$Addr,$Host,$Problem) = ($ThisLine =~ /DNS format error from ([.0-9a-fA-F:]+)(?:#\d+)? resolving ([^ \/]+)(?:\/[^ ]+)? for(?: client)? ([^ #]+)(?:#\d+)?: (.*) -- invalid response/) ) { $FormatError{$Problem}{$Addr}{$Server}++; } elsif ( ($Server,$Addr,$Host,$Problem) = ($ThisLine =~ /DNS format error from ([.0-9a-fA-F:]+)(?:#\d+)? resolving ([^ \/]+)(?:\/[^ ]+)? for(?: client)? ([^ #]+)(?:#\d+)?: (.*)/) ) { $FormatError{$Problem}{$Addr}{$Server}++; } elsif ( ($ThisLine =~ /found [0-9]* CPU(s)?, using [0-9]* worker thread(s)?/) ) { chomp($ThisLine); $StartLog{$ThisLine}++; } elsif ( (($File,$Line,$Problem) = ($ThisLine =~ /\/etc\/(rndc.key|named.conf):([0-9]+): (unknown option '[^ ]*')/)) or (($File,$Line,$Problem) = ($ThisLine =~ /\/etc\/(rndc.key|named.conf):([0-9]+): ('[^ ]' expected near end of file)/)) or (($File,$Line,$Problem) = ($ThisLine =~ /\/etc\/(named.*.conf):([0-9]+): (.*)/)) or (($File,$Line,$Problem) = ($ThisLine =~ /()()(could not configure root hints from '.*': file not found)/))) { $ConfProb{$File}{"$Line,$Problem"}++; } elsif ( (($ErrorText) = ($ThisLine =~ /^(RUNTIME_CHECK.*)/))or (($ErrorText) = ($ThisLine =~ /^(.* REQUIRE.* failed.*)$/)) or (($ErrorText) = ($ThisLine =~ /(.*: fatal error)/)) or (($ErrorText) = ($ThisLine =~ /(.*: out of memory)/)) ) { $NError{$ErrorText}++; } elsif ( (($ErrorText) = ($ThisLine =~ /^(GeoIP .* DB not available)/)) ) { $GeoIPError{$ErrorText}++; } elsif ( (($ErrorText) = ($ThisLine =~ /^(internal_accept: fcntl\(\) failed: Too many open files)/)) or (($ErrorText) = ($ThisLine =~ /^(socket: too many open file descriptors)/)) ) { $ErrOpenFiles{$ErrorText}++; } elsif ( ($From,$Log) = ($ThisLine =~ /invalid command from ([\.0-9a-fA-F:]*)#[0-9]*: (.*)/) ) { $CCMessages{"$From,$Log"}++; } elsif ( (($Log) = ($ThisLine =~ /(freezing .*zone.*)/)) or (($Log) = ($ThisLine =~ /(thawing .*zone.*)/)) ) { $CCMessages2{$Log}++; } elsif (($CCC) = ($ThisLine =~ /unknown control channel command '(.*)'/)) { $UnknownCCCommands{$CCC}++; } elsif (($CCC) = ($ThisLine =~ /received control channel command '(.*)'/)) { $CCCommands{$CCC}++; } elsif (($Name,$Address) = ($ThisLine =~ /(?:error \()?network unreachable\)? resolving '(.*)': (.*)/)) { $NUR{$Name}{$Address}++; } elsif (($Name,$Address) = ($ThisLine =~ /(?:error \()?host unreachable\)? resolving '(.*)': (.*)/)) { $HUR{$Name}{$Address}++; } elsif (($Client) = ($ThisLine =~ /client ([\da-fA-F.:]+)(?:#\d*:)? notify question section contains no SOA/)) { $NoSOA{$Client}++; } elsif (($Addr) = ($ThisLine =~ /bad cookie from (.*)/)) { $BadCookie{$Addr}++; } elsif (($Addr) = ($ThisLine =~ /missing expected cookie from ([\da-fA-F.:]+)(?:#\d*)?/)) { $MissingCookie{$Addr}++; } elsif (($Entry) = ($ThisLine =~ /shut down hung fetch while resolving '(.*)'/)) { $Timeout{$Entry}++; } elsif (($Entry) = ($ThisLine =~ /loop detected resolving '(.*)'/)) { $LoopDetected{$Entry}++; } elsif (($Hint) = ($ThisLine =~ /checkhints: (.*)/) ) { $Hints{$Hint}++; } elsif (($Response,$Net,$Zone) = ($ThisLine =~/limit(:?ing)? (.+) responses to (\S+)(?: for (.+) \()?/)) { $Zone = "None" unless defined($Zone); $Limit{$Zone}{$Response}{$Net}++ if $Detail; } elsif (($Client,$Response,$Net,$Zone) = ($ThisLine =~/client ([^#]+)(?:#\d+)? \(.*\): (?:view \w+: )?\s*rate limit drop (.+) response to (\S+)(?: for (\S+))?/)) { $Zone = "None" unless defined($Zone); $LimitDrop{$Zone}{$Response}{$Net}{$Client}++ if $Detail; } elsif (($Client,$Response,$Net,$Zone) = ($ThisLine =~/client ([^#]+)(?:#\d+)? \(.*\): (?:view \w+: )?\s*rate limit slip (.+) response to (\S+)(?: for (\S+))?/)) { $Zone = "None" unless defined($Zone); $LimitSlip{$Zone}{$Response}{$Net}{$Client}++ if $Detail; } elsif (($Net,$Zone,$Response) = ($ThisLine =~/limit responses to (\S+)(?: for (\S+))? (.*) +\(/)) { $Zone = "None" unless defined($Zone); $Limit{$Zone}{$Response}{$Net}++ if $Detail; } elsif (($Client,$Net,$Zone,$Response) = ($ThisLine =~/client ([^#]+)(?:#\d+)? \(.*\): (?:view \w+: )?\s*rate limit drop response to (\S+)(?: for (\S+))? (.*) +\(/)) { $Zone = "None" unless defined($Zone); $LimitDrop{$Zone}{$Response}{$Net}{$Client}++ if $Detail; } elsif (($Client,$Net,$Zone,$Response) = ($ThisLine =~/client ([^#]+)(?:#\d+)? \(.*\): (?:view \w+: )?\s*rate limit slip response to (\S+)(?: for (\S+))? (.*) +\(/)) { $Zone = "None" unless defined($Zone); $LimitSlip{$Zone}{$Response}{$Net}{$Client}++ if $Detail; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating \@0x[[:xdigit:]]+: (.*) (\w+): got insecure response; parent indicates it should be secure/)) { $DNSSECInsec{'__Total__'}++; $DNSSECInsec{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating \@0x[[:xdigit:]]+: (.*) (\w+): no valid signature found/)) { $DNSSECInvalid{'__Total__'}++; $DNSSECInvalid{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating \@0x[[:xdigit:]]+: (.*) (\w+): bad cache hit/)) { $DNSSECBadCache{'__Total__'}++; $DNSSECBadCache{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating \@0x[[:xdigit:]]+: (.*) (\w+): verify failed due to bad signature/)) { $DNSSECInvalid{'__Total__'}++; $DNSSECInvalid{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating ([^\/]*)\/(\w+): got insecure response; parent indicates it should be secure/)) { $DNSSECInsec{'__Total__'}++; $DNSSECInsec{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating ([^\/]*)\/(\w+): no valid signature found/)) { $DNSSECInvalid{'__Total__'}++; $DNSSECInvalid{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating ([^\/]*)\/(\w+): verify failed due to bad signature/)) { $DNSSECInvalid{'__Total__'}++; $DNSSECInvalid{$Zone}{$RR}++; } elsif (($Zone,$RR) = ($ThisLine =~ /^(?:view \w+: )?\s*validating ([^\/]*)\/(\w+): bad cache hit/)) { $DNSSECBadCache{'__Total__'}++; $DNSSECBadCache{$Zone}{$RR}++; } elsif (($Error,$Host) = ($ThisLine =~ /^(?:error \()?(.*)\)? resolving '([^']+)':/)) { $DNSSECError{$Error}{'__Total__'}++; $DNSSECError{$Error}{$Host}++; } elsif ($ThisLine =~ /^samba_dlz:/) { if ( ($Rhost, $Error) = ($ThisLine =~ /disallowing update of signer=.* name=(.*) type=.* error=(.*)/ )) { $UpdateDenied{"$Rhost ($Error)"}++; } # ignore rest of samba4 dlz entries for now } else { # Report any unmatched entries... # remove PID from named messages $ThisLine =~ s/(client [\.0-9a-fA-F:]+)\S+/$1/; chomp($ThisLine); $OtherList{$ThisLine}++; } } ####################################### if ( keys %ZoneExpired ) { print "\nZones expired:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneExpired) { print " $ThisOne: $ZoneExpired{$ThisOne} Time(s)\n"; } } if ( keys %ZoneNotLoaded ) { print "\nZones not loaded due to errors:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneNotLoaded) { print " $ThisOne: $ZoneNotLoaded{$ThisOne} Time(s)\n"; } } if ( keys %FailedZoneTransfers ) { my $first = 1; foreach my $Host (keys %FailedZoneTransfers) { my $firsthost = 1; foreach my $Zone (keys %{$FailedZoneTransfers{$Host}}) { if ($FailedZoneTransfers{$Host}{$Zone} >= $FailedZoneTransferThreshold) { if ($first) { print "\nFailed Zone Transfers"; print " (with threshold >= $FailedZoneTransferThreshold)" if $FailedZoneTransferThreshold; print ":\n"; $first = 0; } if ($firsthost) { print " $Host:\n"; $firsthost = 0; } print " $Zone: $FailedZoneTransfers{$Host}{$Zone} Time(s)\n"; } } } } if ( keys %DeniedZoneTransfers ) { print "\nDenied Zone Transfers:\n"; foreach my $Host (keys %DeniedZoneTransfers) { print " $Host:\n"; foreach my $Zone (keys %{$DeniedZoneTransfers{$Host}}) { print " $Zone: $DeniedZoneTransfers{$Host}{$Zone} Time(s)\n"; } } } if ( keys %UpdateDenied ) { print "\nZone update refused:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %UpdateDenied) { print " $ThisOne: $UpdateDenied{$ThisOne} Time(s)\n"; } } if ( keys %UpdateForwardingDenied ) { print "\nZone update forwarding refused:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %UpdateForwardingDenied) { print " $ThisOne: $UpdateForwardingDenied{$ThisOne} Time(s)\n"; } } if ( keys %InsecUpdate ) { print "\nInsecure zones (dynamic update allowed by IP address):\n"; foreach my $ThisOne (sort {$a cmp $b} keys %InsecUpdate) { print " " . $ThisOne . ": " . $InsecUpdate{$ThisOne} . " Time(s)\n"; } } if ( keys %JournalFail ) { print "\nJournal update failed:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %JournalFail) { print " " . $ThisOne . ": " . $JournalFail{$ThisOne} . " Time(s)\n"; } } if (keys %ConfProb) { print "\nErrors in configuration files\n"; foreach my $File (sort keys %ConfProb) { if ($File =~ /.+/) { print " file " . $File . "\n"; foreach (keys %{$ConfProb{$File}}) { my ($Line,$Problem) = split ","; print " " . $File . ":" . "$Line" . ": " . $Problem . ": " . $ConfProb{$File}{"$Line,$Problem"} . " Time(s)\n"; } } else { foreach (keys %{$ConfProb{$File}}) { my ($Line,$Problem) = split ","; print " " . $Problem . ": " . $ConfProb{$File}{"$Line,$Problem"} . " Time(s)\n"; } } } } if (keys %NError) { print "\nErrors:\n"; foreach my $ThisOne (keys %NError) { print " " . $ThisOne . ": " . $NError{$ThisOne} . " Time(s)\n"; } } if (keys %ErrOpenFiles) { print "\nThe following seams to be caused by the patches for CVE-2008-1447."; print "\nPlease update your bind.\n"; foreach my $ThisOne (keys %ErrOpenFiles) { print " " . $ThisOne . ": " . $ErrOpenFiles{$ThisOne} . " Time(s)\n"; } } if ($TCPClientQuota) { print "\nTCP Client Quota hit: $TCPClientQuota Time(s)\n"; } if (keys %Limit) { print "\nRate Limiting occurred for:\n"; foreach my $Zone (keys %Limit) { print " $Zone:\n"; foreach my $Response (keys %{$Limit{$Zone}}) { print " $Response:\n"; foreach my $Net (keys %{$Limit{$Zone}{$Response}}) { print " $Net: $Limit{$Zone}{$Response}{$Net} Time(s)\n"; foreach my $Client (keys %{$LimitDrop{$Zone}{$Response}{$Net}}) { print " Dropped $Client: $LimitDrop{$Zone}{$Response}{$Net}{$Client} Time(s)\n"; } foreach my $Client (keys %{$LimitSlip{$Zone}{$Response}{$Net}}) { print " Slipped $Client: $LimitSlip{$Zone}{$Response}{$Net}{$Client} Time(s)\n"; } } } } } if ( ( $Detail >= 5 ) and (keys %GeoIPError) ) { print "\nGeoIP Errors:\n"; foreach my $ThisOne (keys %GeoIPError) { print " " . $ThisOne . ": " . $GeoIPError{$ThisOne} . " Time(s)\n"; } } if ((keys %CCMessages)){ print "\nInvalid command messages from control channel\n"; foreach (keys %CCMessages) { my ($From,$Log) = split ","; print " " . $From . ": " . $Log . ": " . $CCMessages{"$From,$Log"} . " Time(s)\n"; } } if ( ($Detail >=5) and (keys %JournalRemoved) ) { print "\nJournal file out of date and removed:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %JournalRemoved) { print " " . $ThisOne . ": " . $JournalRemoved{$ThisOne} . " Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %CCMessages2)){ print "\nMessages from control channel\n"; foreach my $ThisOne (keys %CCMessages2) { print " " . $ThisOne . ": " . $CCMessages2{$ThisOne} . " Time(s)\n"; } } if ( ( $Detail >= 5 ) and ($StartNamed) ) { print "\nNamed started: $StartNamed Time(s)\n"; } if ( ( $Detail >= 5 ) and ($ReloadNamed) ) { print "Named reloaded: $ReloadNamed Time(s)\n"; } if ( ( $Detail >= 5 ) and ($ShutdownNamed) ) { print "Named shutdown: $ShutdownNamed Time(s)\n"; } if ( ( $Detail >= 5 ) and ($ShutdownNamedFail) ) { print "Named shutdown failed: $ShutdownNamedFail Time(s)\n"; } if ( ( $Detail >= 5 ) and (keys %ZoneLoaded) ) { print "\nLoaded Zones:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneLoaded) { print " $ThisOne: $ZoneLoaded{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %PolicyZoneChanged) ) { print "\nPolicy Zone Changes:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %PolicyZoneChanged) { print " $ThisOne: $PolicyZoneChanged{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %ZoneReceivedNotify) ) { print "\nZones receiving notify:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneReceivedNotify) { print " $ThisOne: $ZoneReceivedNotify{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %ZoneRefusedNotify) ) { print "\nZones refused notify:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneRefusedNotify) { print " $ThisOne: $ZoneRefusedNotify{$ThisOne} Time(s)\n"; } } if ( ($Detail >= 5) and (keys %ChannelAddFail) ) { print "\nCan't add command channel:\n"; foreach my $Channel (sort {$a cmp $b} keys %ChannelAddFail) { print " $Channel:\n"; foreach my $Reason (sort {$a cmp $b} keys %{$ChannelAddFail{$Channel}}) { print " $Reason: $ChannelAddFail{$Channel}{$Reason} Time(s)\n"; } } } if ( ($Detail >= 5) and (keys %MasterFailure) ) { print "\nFailure trying to refresh zone:\n"; foreach my $Zone (sort {$a cmp $b} keys %MasterFailure) { print " $Zone:\n"; foreach my $Reason (sort {$a cmp $b} keys %{$MasterFailure{$Zone}}) { print " $Reason: $MasterFailure{$Zone}{$Reason} Time(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %DeferredZoneTransfers) ) { print "\nDeferred Zone Transfers:\n"; foreach my $Zone (keys %DeferredZoneTransfers) { print " $Zone: $DeferredZoneTransfers{$Zone} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %ZoneRemoved) ) { print "\nRemoved Zones:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneRemoved) { print " $ThisOne: $ZoneRemoved{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %Rewrites) ) { print "\nLookup rewrites:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %Rewrites) { print " $ThisOne: $Rewrites{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %AXFR) ) { print "\nZone Transfers:\n"; foreach my $ThisOne (keys %AXFR) { print " Zone: $ThisOne\n"; foreach my $Temp (keys %{$AXFR{$ThisOne}}) { print " by $Temp: $AXFR{$ThisOne}{$Temp} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %BadZone) ) { print "\nBad Zone Transfer Request:\n"; foreach my $Reason (keys %BadZone) { print " Reason: $Reason\n"; foreach my $ThisOne (sort {$a cmp $b} (keys %{$BadZone{$Reason}}) ) { print " $ThisOne: $BadZone{$Reason}{$ThisOne} Time(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %DeniedTCPClient) ) { print "\nno more TCP clients warning:\n"; foreach my $ThisOne (keys %DeniedTCPClient) { print " from $ThisOne: $DeniedTCPClient{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %DeniedQuery) ) { print "\nQueries (cached) that were denied:\n"; foreach my $ThisOne (keys %DeniedQuery) { print " from $ThisOne: $DeniedQuery{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 10 ) and (keys %DeniedQueryNoCache) ) { print "\nQueries (not cached) that were denied:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %DeniedQueryNoCache) { print " from $ThisOne: $DeniedQueryNoCache{$ThisOne} Time(s)\n"; } } if ( ( $Detail >= 10 ) and (@CNAMEAndOther) ) { print "\nThese hosts have CNAME and other data (invalid):\n"; foreach my $ThisOne (@CNAMEAndOther) { print " $ThisOne\n"; } } if ( ( $Detail >= 5 ) and (keys %ZoneFileErrors) ) { print "\nSyntax errors in zone files:\n"; for my $File (keys %ZoneFileErrors) { print " $File\n"; for my $Error ( keys %{$ZoneFileErrors{$File}} ) { print " \"$Error\" " . $ZoneFileErrors{$File}{$Error} . " Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %ConnectionRefused) ) { print "\nConnection refused resolving:\n"; foreach my $Addr (sort keys %ConnectionRefused) { print " $Addr:\n"; foreach my $Server (sort SortIP keys %{$ConnectionRefused{$Addr}}) { print " $Server: $ConnectionRefused{$Addr}{$Server} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %LameServer) ) { print "\nThese addresses had lame server references:\n"; foreach my $Addr (sort keys %LameServer) { print " $Addr:\n"; foreach my $Server (sort SortIP keys %{$LameServer{$Addr}}) { print " $Server: $LameServer{$Addr}{$Server} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %NonAuthoritative) ) { print "\nNon-authoritative answer from master for these zones:\n"; foreach my $ThisOne (keys %NonAuthoritative) { print " " . $ThisOne . ": " . $NonAuthoritative{$ThisOne} . " Time(s)\n"; } } if ( ( $Detail >= 10 ) and (keys %BadCookie) ) { print "\nBad Cookie returned from these hosts:\n"; foreach my $ThisOne (keys %BadCookie) { print " " . $ThisOne . ": " . $BadCookie{$ThisOne} . " Time(s)\n"; } } if ( ( $Detail >= 10 ) and (keys %MissingCookie) ) { print "\nMissing expected Cookie returned from these hosts:\n"; foreach my $ThisOne (keys %MissingCookie) { print " " . $ThisOne . ": " . $MissingCookie{$ThisOne} . " Time(s)\n"; } } if ( ($Detail >= 10) and (keys %RetryLimit) ) { print "\nRetry limit exceeded for these zones:\n"; foreach my $Zone (sort {$a cmp $b} keys %RetryLimit) { print " $Zone: $RetryLimit{$Zone} Time(s)\n"; } } if ( ($Detail >= 10) and (keys %NoSOA) ) { print "\nNotify question sections of these clients contained no SOA:\n"; foreach my $Client (sort {$a cmp $b} keys %NoSOA) { print " $Client: $NoSOA{$Client} Time(s)\n"; } } if ( ( $Detail >= 10 ) and (keys %NetworkUnreachable) ) { print "\nNetwork is unreachable for:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %NetworkUnreachable) { print " $ThisOne:\n"; foreach my $Host (sort {$a cmp $b} keys %{$NetworkUnreachable{$ThisOne}}) { print " $Host: $NetworkUnreachable{$ThisOne}{$Host} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %NUR) ) { print "\nNetwork unreachable resolving for:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %NUR) { print " $ThisOne:\n"; foreach my $Host (sort {$a cmp $b} keys %{$NUR{$ThisOne}}) { print " $Host: $NUR{$ThisOne}{$Host} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %HUR) ) { print "\nHost unreachable resolving for:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %HUR) { print " $ThisOne:\n"; foreach my $Host (sort {$a cmp $b} keys %{$HUR{$ThisOne}}) { print " $Host: $HUR{$ThisOne}{$Host} Time(s)\n"; } } } if ( ( $Detail >= 10 ) and (keys %Timeout) ) { print "\nTimeout resolving for:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %Timeout) { print " " . $ThisOne . ": " . $Timeout{$ThisOne} . " Time(s)\n"; } } if ( ( $Detail >= 10 ) and (keys %LoopDetected) ) { print "\nLoop detected resolving for:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %LoopDetected) { print " " . $ThisOne . ": " . $LoopDetected{$ThisOne} . " Time(s)\n"; } } if ( ( $Detail >= 10 ) and (keys %ZoneUpdates) ) { print "\nZone Updates:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ZoneUpdates) { print " $ThisOne:\n"; foreach my $Message (sort {$a cmp $b} keys %{$ZoneUpdates{$ThisOne}}) { print " $Message: $ZoneUpdates{$ThisOne}{$Message} Time(s)\n"; } } } if (($Detail >= 5) and (keys %UnexpRCODE)) { print "\nUnexpected DNS RCODEs:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %UnexpRCODE) { print " " . $ThisOne . ":\n"; foreach my $Zone (sort {$a cmp $b} keys %{$UnexpRCODE{$ThisOne}}) { print " " . $Zone . ":\n"; foreach my $Host (sort SortIP keys %{$UnexpRCODE{$ThisOne}{$Zone}}) { print " " . $Host . ": " . $UnexpRCODE{$ThisOne}{$Zone}{$Host} . " Time(s)\n"; } } } } if (($Detail >= 5) and (keys %FormErr)) { print "\nIncorrect response format:\n"; foreach my $ThisOne (keys %FormErr) { print " " . $ThisOne . ": " . $FormErr{$ThisOne} . " Time(s)\n"; } } if (($Detail >= 5) and (keys %FormatError)) { print "\nDNS Format Error:\n"; foreach my $Problem (sort {$a cmp $b} keys %FormatError) { print " " . $Problem . ":\n"; foreach my $Addr (sort {$a cmp $b} keys %{$FormatError{$Problem}}) { print " " . $Addr . ":\n"; foreach my $Server (sort SortIP keys %{$FormatError{$Problem}{$Addr}}) { print " " . $Server . ": " . $FormatError{$Problem}{$Addr}{$Server} . " Time(s)\n"; } } } } if (($Detail >= 10) and (keys %StartLog)) { print "\nNamed startup logs:\n"; foreach my $ThisOne (keys %StartLog) { print " " . $ThisOne . ": " . $StartLog{$ThisOne} . " Time(s)\n"; } } if (($Detail and (keys %CCCommands)) or (keys %UnknownCCCommands)) { print "\nReceived control channel commands\n"; foreach my $ThisOne (keys %CCCommands) { print " " . $ThisOne . ": " . $CCCommands{$ThisOne} . " Time(s)\n"; } foreach my $ThisOne (keys %UnknownCCCommands) { print " " . $ThisOne . "(unknown command): " . $CCCommands{$ThisOne} . " Time(s)\n"; } } if (keys %Hints) { print "\nCheckhints:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %Hints) { print " " .$ThisOne .": $Hints{$ThisOne} Time(s)\n"; } } if (($Detail >= 5) and (keys %DNSSECInsec)) { print "\nDNSSEC Insecure Responses: " . $DNSSECInsec{'__Total__'} . " Time(s)\n"; foreach my $Zone (sort keys %DNSSECInsec) { if (($Detail >= 10) and ($Zone =~ /.+/) and ($Zone ne '__Total__')) { foreach my $RR (sort keys %{$DNSSECInsec{$Zone}}) { print " " . "$Zone/$RR: " . $DNSSECInsec{$Zone}{$RR} . " Time(s)\n"; } } } } if (($Detail >= 5) and (keys %DNSSECInvalid)) { print "\nDNSSEC No Valid Signature: " . $DNSSECInvalid{'__Total__'} . " Time(s)\n"; foreach my $Zone (sort keys %DNSSECInvalid) { if (($Detail >= 10) and ($Zone =~ /.+/) and ($Zone ne '__Total__')) { foreach my $RR (sort keys %{$DNSSECInvalid{$Zone}}) { print " " . "$Zone/$RR: " . $DNSSECInvalid{$Zone}{$RR} . " Time(s)\n"; } } } } if (($Detail >= 5) and (keys %DNSSECBadCache)) { print "\nDNSSEC Bad Cache hit: " . $DNSSECBadCache{'__Total__'} . " Time(s)\n"; foreach my $Zone (sort keys %DNSSECBadCache) { if (($Detail >= 10) and ($Zone =~ /.+/) and ($Zone ne '__Total__')) { foreach my $RR (sort keys %{$DNSSECBadCache{$Zone}}) { print " " . "$Zone/$RR: " . $DNSSECBadCache{$Zone}{$RR} . " Time(s)\n"; } } } } if (($Detail >= 5) and (keys %DNSSECError)) { print "\nDNSSEC Errors:\n"; foreach my $Error (sort keys %DNSSECError) { print " $Error: " . $DNSSECError{$Error}{'__Total__'} . " Time(s)\n"; if ($Detail >= 10) { foreach my $Host (sort keys %{$DNSSECError{$Error}}) { print " " . "$Host: " . $DNSSECError{$Error}{$Host} . " Time(s)\n" unless ($Host eq '__Total__'); } } } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/resolver0000644000175000017500000000675013420545435020572 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ############################################################################# #Copyright (c) 2004, Sparta, Inc #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions are met: # #* Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # #* Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # #* Neither the name of Sparta, Inc nor the names of its contributors may # be used to endorse or promote products derived from this software # without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS #IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, #THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR #PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR #CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, #EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, #PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; #OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, #WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR #OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF #ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################# ############################################################################# # These scripts were created as part of the dnssec-tools project. # For more information, see http://sourceforge.net/dnssec-tools. # Detailed instructions for setting up BIND 9.3.* to use these logwatch # configuration files and scripts are contained in the README file # on sourceforge. ############################################################################# use strict; my $detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $valComplete; my $valOK; my $nonextValOK; while (defined(my $ThisLine = )) { if ($ThisLine =~ /validation completion event/) { $valComplete++; } elsif ($ThisLine =~ /nonexistence validation OK/) { $nonextValOK++; } elsif ($ThisLine =~ /validation OK/) { $valOK++; } } if ($detail >= 5) { my %msgHash = (); print "\n"; if ($valComplete > 0) { $msgHash{"Received validation completion event "} = $valComplete; } if ($nonextValOK > 0) { $msgHash{"Nonexistence validation OK received "} = $nonextValOK; } if ($valOK > 0) { $msgHash{"Validation OK "} = $valOK; } # sort all the non-zero message types and print them in descending order # of number of occurrences my $key; foreach $key (sort { $msgHash{$b} <=> $msgHash{$a} } keys %msgHash) { print " " . $key . " " . $msgHash{$key} . " time(s)\n"; } } exit (0); # vi: shiftwidth=3 tabstop=3 et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/pam_pwdb0000644000175000017500000002075615075123620020520 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ##################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### # NOTE: This script is for older (6.X era) Red Hat boxes use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; #Init String Containers my ( $EndName, $From, $Num, $Orig, $RemoteHost, $Service, $ServiceName, $StartName, $StartUID, $Temp, $ThisKey, $User ); #Init Arrays my (@BadName, @Expired, @PWChanges) = (); #Init Hashes my ( %AuthFailures, %CouldNotIDPW, %FailedLogins, %LocalLogins, %OpenedSessions, %OtherList, %RemoteLogins, %RootLogins, %SUList ); while (defined(my $ThisLine = )) { if ( ( $ThisLine =~ /(.*) session closed for user .*$/ ) or ( $ThisLine =~ /^Logout user .* host .*$/ ) or ( $ThisLine =~ /^log: Password authentication/ ) or # apparently SSHD messages ( $ThisLine =~ /^log: Closing connection/ ) or # apparently SSHD messages ( $ThisLine =~ /^check pass; user unknown/ ) or ( $ThisLine =~ /^User account has expired/ ) or # This one is caught below (2-line message) ( $ThisLine =~ /^get passwd; pwdb: structure is no longer valid$/) or ( $ThisLine =~ /^fatal: Read error from remote host: Connection reset by peer$/) or ( $ThisLine =~ /^new password not acceptable$/ ) or ( $ThisLine =~ /^FTP session closed$/) or ( $ThisLine =~ /^FTP LOGIN REFUSED/) or ( $ThisLine =~ /^FAILED LOGIN SESSION FROM \S+ FOR , Error in service module/) or ( $ThisLine =~ /^FTP LOGIN FROM/ ) # I will let ftpd handle FTP messages.... ) { # We don't care about these } elsif ( $ThisLine =~ /password for \(.*\) changed by \(.*\)$/ ) { chomp($ThisLine); push @PWChanges, $ThisLine; } elsif ( ($RemoteHost,$User) = ( $ThisLine =~ /^failed login from ([^ ]+) \[.*\], ([^ ]+)$/) or ($RemoteHost,$User) = ( $ThisLine =~ /^Login failure user=(\S+) host=([^ ]+)$/) ) { chomp ($User); push @{$FailedLogins{$RemoteHost}}, $User; } elsif ( $ThisLine =~ s/auth could not identify password for \[([^ ]+)\]$/$1/ ) { chomp ($ThisLine); $CouldNotIDPW{$ThisLine}++; } elsif ( $ThisLine =~ s/^expiry check failed for \'([^ ]+)\'/$1/ ) { # user account expired? chomp($ThisLine); push @Expired, $ThisLine; } elsif ( $ThisLine =~ s/bad username \[(.*)\]$/$1/ ) { chomp($ThisLine); push @BadName, $ThisLine; } elsif ( ($StartName,$StartUID,$EndName,$ServiceName) = ( $ThisLine =~ m/authentication failure.*; ([^ ]*)\(uid=(\d+)\) -> ([^ ]+) for ([^ ]+) service$/ ) ) { $StartName = " " if (!$StartName); $ThisKey = $StartName . "(uid=" . $StartUID . ") -> " . $EndName; $AuthFailures{$ThisKey}{$ServiceName}++; } elsif ( ($Num,$StartName,$StartUID,$EndName,$ServiceName) = ( $ThisLine =~ m/^(\d+) authentication failure.*; ([^ ]*)\(uid=(\d+)\) -> ([^ ]+) for ([^ ]+) service$/ ) ) { $StartName = " " if (!$StartName); $ThisKey = $StartName . "(uid=" . $StartUID . ") -> " . $EndName; $AuthFailures{$ThisKey}{$ServiceName}+=$Num; } elsif ( ($ThisKey,$ServiceName) = ( $ThisLine =~ /([^ ]+) authentication failed for ([^ ]+)$/ ) ) { chomp($ThisKey); chomp($ServiceName); $AuthFailures{$ThisKey}{$ServiceName}++; } elsif ( ($RemoteHost, $User) = ( $ThisLine =~ m/^FAILED LOGIN .* FROM ([^ ]+) FOR (.+), .*$/ ) ) { push @{$FailedLogins{$RemoteHost}}, $User; } elsif ( $ThisLine =~ s/^ROOT LOGIN ON ([^ ]+)/$1/ ) { chomp ($ThisLine); $RootLogins{$ThisLine}++; } elsif ( ($User,$From) = ( $ThisLine =~ /^LOGIN ON [^ ]+ BY ([^ ]+) FROM ([^ ]+)$/ ) or ($User,$From) = ( $ThisLine =~ /^Login user=([^ ]+) host=([^ ]+)$/ ) ) { chomp ($From); ${$RemoteLogins{$User}}{$From}++; } elsif ( $ThisLine =~ s/^LOGIN ON [^ ]+ BY ([^ ]+$)/$1/ ) { chomp ($ThisLine); $LocalLogins{$ThisLine}++; } elsif ( ($ServiceName,$StartName,$StartUID,$EndName) = ( $ThisLine =~ m/([^ ]+)\[[0-9]+\]:\s+authentication\s+failure;\s*logname=([^ ]+)\s+uid=([^ ]+).*user=([^ ]+)/ ) ) { $StartName = " " if (!$StartName); $ThisKey = $StartName . "(uid=" . $StartUID . ") -> " . $EndName; $AuthFailures{$ThisKey}{$ServiceName}++; } elsif ( ($Service, $User, $Orig) = ( $ThisLine =~ /^\((.*)\) session opened for user ([^ ]+) by (.*\(uid=.*\))/ ) ) { if (( $Service eq "su" ) and ($Orig =~ /[^ ]+\(uid=.*\)$/)) { $Temp = " " . $Orig . " -> " . $User; $SUList{$Temp}++; } else { ${$OpenedSessions{$Service}}{$User}++; } } else { # Report any unmatched entries... chomp($ThisLine); $OtherList{$ThisLine}++; } } if (keys %SUList) { print "\nSU Sessions:\n"; foreach my $SU (keys %SUList) { print " " . $SU . " - " . $SUList{$SU} . " Time(s)\n"; } } if (($Detail >= 10) and (keys %CouldNotIDPW)) { print "\nCould not identify password for:\n"; foreach my $User (keys %CouldNotIDPW) { print " " . $User . " - " . $CouldNotIDPW{$User} . " Time(s)\n"; } } if (@PWChanges) { print "\nPassword Changes:\n"; foreach my $Change (@PWChanges) { print " " . $Change . "\n"; } } if (($Detail >= 5) and (@BadName)) { print "\nBad Usernames Received:\n"; foreach my $User (@BadName) { print " " . $User . "\n"; } } if (@Expired) { print "\nExpired User Accounts:\n"; foreach my $User (@Expired) { print " " . $User . "\n"; } } if (keys %OpenedSessions) { print "\nOpened Sessions:\n"; foreach my $Service (keys %OpenedSessions) { print " Service: " . $Service . "\n"; foreach my $User (keys %{$OpenedSessions{$Service}}) { print " User " . $User . " - " . ${$OpenedSessions{$Service}}{$User} . " Time(s)\n"; } } } if (keys %RemoteLogins) { print "\nRemote Logins:\n"; foreach my $User (keys %RemoteLogins) { print " User " . $User . ":\n"; foreach my $Remote (keys %{$RemoteLogins{$User}} ) { print " Remote Host " . $Remote . " - " . ${$RemoteLogins{$User}}{$Remote} . " Time(s)\n"; } } } if (keys %LocalLogins) { print "\nLocal Logins:\n"; foreach my $User (keys %LocalLogins) { print " " . $User . " - " . $LocalLogins{$User} . " Time(s)\n"; } } if (keys %RootLogins) { print "\nRoot Logins:\n"; foreach my $tty (keys %RootLogins) { print " " . $tty . ": " . $RootLogins{$tty} . " time(s)\n"; } } if (($Detail >= 5) and (keys %AuthFailures)) { print "\nAuthentication Failures:\n"; foreach my $Users (keys %AuthFailures) { print " " . $Users . "\n"; foreach my $Service (keys %{$AuthFailures{$Users}}) { print " Service: " . $Service . ": " . ${$AuthFailures{$Users}}{$Service} . " time(s)\n"; } } } if (($Detail >= 5) and (keys %FailedLogins)) { print "\nLogin Failures:\n"; foreach my $RemoteHost (keys %FailedLogins) { print " " . $RemoteHost . ": "; foreach my $User ( @{$FailedLogins{$RemoteHost}} ) { print $User . ", "; } print "\n"; } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print "$line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/sendmail0000644000175000017500000027552015122642254020526 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use diagnostics; use strict; use Logwatch ':sort'; use Errno; use POSIX qw(strerror); # PrettyHost decomposes host names and IP addresses and # formats them to align vertically sub PrettyHost { # $_[0] is the line to format my $Line = $_[0]; # $_[1] is the length available my $LineLength = $_[1]; # if ((not defined $main::sendmail_prettyhost) or ($main::sendmail_prettyhost == 0)) { return($Line); } my ($Name, $Addr, $Other) = ($Line =~ /^\s*(.*?)\s*(\[[\d\.:]*\])\s*(.*?)\s*$/); if (index($Line, "\[") < 0) { $Name = $Line; } if (not defined $Name) {$Name=""}; if (not defined $Addr) {$Addr=""}; if (not defined $Other) {$Other=""}; if ($Other ne "") { $Name = $Name . " " . $Other; } # From LineLength, we will use 18 chars for one space # and a full IPv4 address if (length($Line) > $LineLength) { while ((length($Name) > $LineLength-21) and (($Name =~ tr/\./\./) > 1) ) { $Name =~ s/[^\.]*\.(.*)/$1/; } $Name = "..." . $Name; } sprintf ("%*s %-17s", 18-$LineLength, $Name, $Addr); } # PrettyTimes simply formats the lines with counts "Time(s)" to # align with the results of the PrettyHost routine sub PrettyTimes { # $_[0] is the event to format (string) my $Line = $_[0]; # $_[1] is the number of times it occurs (integer) my $Amount = $_[1]; # if ((not defined $main::sendmail_prettyprint) or ($main::sendmail_prettyprint == 0)) { printf "\n%s: %d Time%s", $Line, $Amount, ($Amount == 1) ? "" : "s"; } else { my $line_length = 72-length($Line); printf "\n%s %*d Time%s", $Line, ($line_length > 0)? $line_length : 0, $Amount, ($Amount == 1) ? "" : "s"; } return 0; } my $LogwatchDetail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $sendmail_milterheaderstocount = $ENV{'sendmail_milterheaderstocount'} || ""; my @MilterHeadersToCount = split(/\|/, $sendmail_milterheaderstocount); my $MatchFilter = $ENV{'sendmail_matchfilter'} || ""; my $ReportFilter = $ENV{'sendmail_reportfilter'} || ""; # Extract formatting directives. If Sendmail_PrettyHost is set, # assume that Sendmail_PrettyPrint is also desired our $sendmail_prettyhost = $ENV{'sendmail_prettyhost'}; if (not defined $sendmail_prettyhost) {$sendmail_prettyhost = 1}; our $sendmail_prettyprint = $ENV{'sendmail_prettyprint'}; if ((not defined $sendmail_prettyprint) || $sendmail_prettyhost) {$sendmail_prettyprint = 1}; my $Detail = $ENV{'sendmail_detail'}; if (not defined $Detail) { print "\n\nDetail Level of output is inherited from conf/logwatch.conf." if $Debug; $Detail = $LogwatchDetail; } else { print "\n\nUsing Detail Level = $Detail from conf/services/sendmail.conf" if $Debug; } #print "\nSee file conf/services/sendmail.conf on how to customize output."; # The following variables are auto-increment counts, so are initialized my $AddrRcpts = my $BytesTransferred = my $CantCreateOutput = my $DaemonThrottle = my $Errno = my $LoadAvgQueueSkip = my $LoadAvgReject = my $MsgsNoRcpt = my $MsgsSent = my $NoMilterFilters = my $NoMoreSpace = my $OutdatedAliasdb = my $OverSize = my $OverSizeBytes = my $RelayLocalhost = my $RemoteProtocolError =my $SendmailStarts = my $SendmailStopped = my $TLSPeerReset = my $TooManyRcpts = my $XS4ALL = 0; # The following variables are always initialized before usage, so they are merely declared here. # (Someday it might be useful to reduce their scope, but most of them are used in the large # if..elsif structure, making that hard. my ( $Address, $Arg, $Attack, $Auth, $BlSite, $Bytes, $CommonName, $DeliverStat, $Dest, $DetailReason, $Domain, $Error, $ErrorCount, $ETRN, $File, $Forward, $FromUser, $Header, $HeaderMod, $Host, $IP, $LastIndex, $LastIndex2, $Load, $Luser, $MailerName, $MailerString, $MailerType, $NewQueueID, $NoCommonName, $NumRcpts, $Owner, $OtherListFound, $QueueID, $Reason, $RejCmd, $Relay, $RelayDeniedCount, $RelayHost, $RelayName, $Ruser, $Size, $Source, $StarttlsCipherEntry, $StarttlsCipherType, $StarttlsMode, $StarttlsNumBits, $StarttlsReason, $StarttlsVerify, $StatError, $StatFile, $Temp, $Temp1, $ThisLine, $ThisOne, $TimeoutSend, $TimeoutSendWarning, $TLSFile, $TLSFrom, $TLSReason, $TotalBytes, $TotalNum, $ToUser, $User, $Usr, $Warning, $Directory, $Cause ); # The following arrays and hashes need to have file-wide scopes my @SizeDist; my ( %Abuse, %AddressError, %AttackAttempt, %AUTHfailure, %AUTHnouser, %AuthWarns, %BadAuth, %BadRcptThrottle, %BlackHoled, %BlackHoles, %CheckMailReject, %CheckRcptReject, %CollectError, %CommandUnrecognized, %DisabledMailbox, %DNSMap, %DomainErrors, %DummyConnection, %ETRNs, %ForwardErrors, %KnownSpammer, %LargeHdrs, %LargeMsgs, %LastCmd, %LoadAvg, %LostInputChannel, %LostQueueFile, %LowSpace, %MailBomber, %MailBomberConn, %Mailers, %MailRejected, %MilterDeferrals, %MilterErrors, %MilterHeaderCount, %Msgs, %NotLocal, %OtherList, %PREGreeting, %PREGreetingQueue, %Quarantined, %RelayDenied, %RelayReject, %ReturnReceipts, %RuleSets, %SaslError, %SenderIDResults, %SentTimeouts, %SortedUsers, %SPFResults, %Starttls, %StarttlsCert, %StarttlsCipher, %StatDeferred, %StatFileError, %StatRejected, %StatRejectedLog, %SysErr, %Timeouts, %TLSAcceptFailed, %TLSConnectFailed, %TLSErrno, %TLSFileMissing, %ToList, %TooManyHops, %UnknownUsers, %UnknownUsersCheckRcpt, %WUnsafe ); # Initialize $SizeDist array for my $i (0..9) { $SizeDist[$i]{'Num'} = 0; $SizeDist[$i]{'Bytes'} = 0; } # QueueID formats: in 8.11 it was \w{7}\d{5}, in 8.12+ it is \w{8}\d{6} # Also, PID can now be up to seven digits in 64-bit systems my $QueueIDFormat = "(?:\\w{7,9}\\d{5,7}|NOQUEUE)"; # ENOENT refers to "no such file or directory" my $ENOENT = Errno::ENOENT(); while (defined($ThisLine = )) { # not all log entries have a queue id ($QueueID) = ($ThisLine =~ /^($QueueIDFormat): /o ); if (defined $QueueID) {$ThisLine =~ s/^$QueueID: //;} # $MatchFilter is a variable that is set by setting the $Sendmail_MatchFilter variable # in the conf/services/sendmail.conf file. It is executed here, before any other # matching statements eval $MatchFilter; if ($@) { print $@; print "While processing MatchFilter:\n$MatchFilter\n"; } # $ThisLine might have been reset (undef, or empty string) in $MatchFilter next unless $ThisLine; if ( # informational statements of little value # file=alias.c, LogLevel>7, LOG_NOTICE ( $ThisLine =~ /^alias database [^ ]* (auto)?rebuilt by/ ) or # file=alias.c, LogLevel>7, LOG_INFO ( $ThisLine =~ /[0-9]* aliases, longest [0-9]* bytes, [0-9]* bytes total/ ) or # file=util.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /^started as: / ) or # file=daemon.c, LogLevel>8, LOG_INFO ( $ThisLine =~ /accepting new messages \(again\)/ ) or # the following is captured later, as detailed info is also printed # file=collect.c, LogLevel>1, LOG_WARNING ( $ThisLine =~ /^collect: premature EOM: / ) or # the following is captured later, as detailed info is also printed # file=milter.c, LogLevel>0, LOG_INFO ( $ThisLine =~ /^Milter \(.*\): to error state$/ ) or # milter statements # file=milter.c, LogLevel>8, LOG_INFO ( $ThisLine =~ /^Milter message: body replaced$/ ) or # file=milter.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /^Milter accept: message$/ ) or # file=milter.c, LogLevel>9, LOG_INFO #( $ThisLine =~ /^Milter \(\w*\): init success to / ) or # milter name may contain none \w symbols such as hyphen ( $ThisLine =~ /^Milter \(\S*\): init success to / ) or # file=milter.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /^Milter: connect/ ) or # file=milter.c, LogLevel>10, LOG_INFO ( $ThisLine =~ /^Milter \(\w*\): abort filter/ ) or # file=milter.c, LogLevel>10, LOG_INFO ( $ThisLine =~ /^milter=\w*, action=\w*, accepted/ ) or # file=milter.c, LogLevel>10, LOG_INFO ( $ThisLine =~ /^milter=\w*, action=\w*, tempfail/ ) or # file=milter.c, LogLevel>12, LOG_INFO ( $ThisLine =~ /^milter=\w*, action=\w*, continue/ ) or # the following is captured later in srvrsmtp.c, except for milter service name # file=milter.c, LogLevel>12, LOG_INFO ( $ThisLine =~ /^milter=\w*, (reject|discard)/ ) or # file=milter.c, LogLevel>14, LOG_INFO ( $ThisLine =~ /^Milter: (rcpts|senders?):/ ) or # file=milter.c, LogLevel>17, LOG_INFO ( $ThisLine =~ /^Milter \(\w*\): (headers|body), sen[dt]/ ) or # file=milter.c, LogLevel>18, LOG_INFO ( $ThisLine =~ /^Milter \(\w*\): quit filter/ ) or # file=milter.c, LogLevel>21, LOG_INFO ( $ThisLine =~ /^Milter \(\w*\): time command / ) or # file=milter.c, LogLevel>21, LOG_INFO ( $ThisLine =~ /^Milter \(\w*\): header, / ) or # the following two return errors that are caught in the "to=" statements # file=srvrsmtp.c, LogLevel>3, LOG_INFO ( $ThisLine =~ /^Milter: data, reject=55[0-9] 5\.7\.1 (.*)/ ) or # file=srvrsmtp.c, LogLevel>3, LOG_INFO ( $ThisLine =~ /^Milter: data, discard$/ ) or # file=srvrsmtp.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /^Sending .* to Milter$/ ) or # file=srvrsmtp.c, LogLevel>3, LOG_INFO # SMTP codes # status code 0XX is informational ( $ThisLine =~ /^--- 0[0-9]{2}(-| )/ ) or # status code 2XX is success - but hold onto the Hello response ( ( $ThisLine =~ /^--- 2[0-9]{2}(-| )/ ) and not ( $ThisLine =~ /^--- 250[ -].* Hello .*, pleased to meet you$/ )) or # status codes 4XX are for transient failures ( ( $ThisLine =~ /^--- 4[0-9]{2}(-| )/ ) and not # but note bad commands, because we'll need it later ( $ThisLine =~ /^--- 421 4\.7\.0 .* Too many bad commands; closing connection$/)) or # status code 334 is used for STARTTLS verification ( $ThisLine =~ /^--- 334 / ) or # status code 354 used to request data ( $ThisLine =~ /^--- 354 Enter mail, end with \"\.\" on a line by itself/ ) or ( $ThisLine =~ /^--- 354 End data with ./) or # invalid smtp commands detected later ($RejCmd) ( $ThisLine =~ /^--- 502 5(\.[0-9]){2} Sorry, we do not allow this operation$/ ) or # Need RCPT most likely because of incorrect RCPT command, in which case ignore it ( ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Need RCPT \(recipient\)$/ ) and ( $Msgs{$QueueID}{"BadRCPT"} > 0)) or ( $ThisLine =~ /^--- 530 5\.7\.0 Authentication required$/ ) or # AUTH failure detected later with %AUTHfailure ( $ThisLine =~ /^--- 535 5\.7\.0 authentication failed$/ ) or # Mailbox disabled detected later by ruleset=check_rcpt ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .* Mailbox disabled for this recipient$/ ) or # bogus HELO detected later by rulteset=check_rcpt ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .* bogus HELO name used/ ) or # Commands rejected are from greet_pause or milter ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} Command rejected$/ ) or # User unknown detected later by ruleset=check_rcpt ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .*\.\.\. User unknown/ ) or # Relaying denied detected later by ruleset=check_rcpt ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .*\.\.\. Relaying denied/ ) or # Access denied detected later by ruleset=check_relay ( $ThisLine =~ /^--- 550 5(\.[0-9]){2} .*\.\.\. Access denied/ ) or # Domain errors detected later by ruleset=check_mail ( $ThisLine =~ /^--- 553 5(\.[0-9]){2} .*\.\.\. Domain of sender address .* does not exist$/ ) or ( $ThisLine =~ /^--- 553 5(\.[0-9]){2} .*\.\.\. Domain name required for sender address/ ) or # detected later as Hostname required ($ThisLine =~ /^--- 553 5(?:\.\d){2} (.*)\.\.\. Hostname required$/ ) or # the following used by milter, which is detected later ( $ThisLine =~ /^--- 554 5\.7\.1 / ) or # the following used by greet_pause feature ( $ThisLine =~ /^--- 554 .* not accepting messages/ ) or # detected by "invalid domain name" statement elsewhere ( $ThisLine =~ /^--- 501 5(\.[0-9]){2} Invalid domain name$/ ) or # out-of-sequence commands ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Polite people say HELO first$/ ) or ( $ThisLine =~ /^--- 501 5(\.[0-9]){2} HELO requires domain address$/ ) or ( $ThisLine =~ /^--- 501 5(\.[0-9]){2} EHLO requires domain address$/ ) or ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Need MAIL command$/ ) or ( $ThisLine =~ /^--- 503 5(\.[0-9]){2} Need MAIL before RCPT$/ ) or # these are the valid commands ( $ThisLine =~ /<-- (EHLO|HELO|STARTTLS|MAIL FROM|RCPT TO|DATA|RSET|QUIT|NOOP)/i ) or ( $ThisLine =~ /<-- (ETRN|VERB|EXPN|VRFY|HELP|AUTH|NOOP|VERB)/i ) or # file=daemon.c, LogLevel>11, LOG_INFO ( $ThisLine =~ /SMTP outgoing connect on/ ) or # file=envelope.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /done; delay=[0-9:\+]*, ntries=/ ) or # file=alias.c, LogLevel>10, LOG_INFO ( $ThisLine =~ /^alias.*=>/ ) or # file=main.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /^connect from / ) or # file=srvrsmtp.c, LogLevel>11, LOG_INFO ( $ThisLine =~ /^AUTH: available mech=/ ) or # we should probably count the following... # file=deliver.c, LogLevel>9, LOG_INFO ( $ThisLine =~ /^AUTH=client, relay=.*, mech=.*, bits=\d*/ ) or # file=srvrsmtp.c, LogLevel>11, LOG_INFO ( $ThisLine =~ /^AUTH=server, relay=.*, authid=.*, mech=.*, bits=\d*/ ) or # we should probably count the following... # file=deliver.c, LogLevel>4, LOG_INFO ( $ThisLine =~ /^discarded$/ ) or # STARTTLS # file=tls.c, LogLevel>14, LOG_INFO ( $ThisLine =~ /^STARTTLS=(server|client), get_verify:/ ) or # file=tls.c, LogLevel>11, LOG_INFO ( $ThisLine =~ /^STARTTLS=(server|client), cert-subject=/ ) or # file=tls.c, LogLevel>13, LOG_INFO ( $ThisLine =~ /^STARTTLS=(server|client), Diffie-Hellman init, key=/ ) or # file=tls.c, LogLevel>12, LOG_INFO ( $ThisLine =~ /^STARTTLS=(server|client), init=1/ ) or # file=deliver.c, LogLevel>13, LOG_INFO ( $ThisLine =~ /^STARTTLS=client, start=ok$/ ) or # file=readcf.c, LogLevel>9, LOG_NOTICE, starting in 8.16.1 # because the features string can be many things, we ignore those # strings that are known to be non-errors. (Other error strings are # possible, but they don't match because they have more arguments, # such as option/value pairs, "error=", "opt=", etc.) # The "stat=" string was added in 8.17.1, and can be non-error # (value of stat=0). ( $ThisLine =~ m/^tls_(srv|clt)_features= (?!too_short|only_one_of_CertFile\/KeyFile_specified)[^,]*, \ (stat=0,\ )?relay=([^\ ])*\ \[.*\]$/x ) or # file=tls.c, LogLevel>13, LOG_DEBUG, starting in 8.16.1 ( $ThisLine =~ /engine=.*, path=.*, ispre=\d+, pre=\d+, initialized=\d+$/ ) or # the following is described in tls.c as a bug in OpenSSL, and # recommends that the error message be ignored (last checked on 8.15.2) # file=tls.c, LogLevel>15, LOG_WARNING ( $ThisLine =~ /^STARTTLS=(server|client), SSL_shutdown not done$/ ) or # and something similar occurs for the one sending the shutdown after the # connection is already closed by the other side # file=tls.c, LogLevel>11, LOG_WARNING ( $ThisLine =~ /^STARTTLS=(server|client), SSL_shutdown failed/ ) or # for reads, the timeout is later captured by either a "lost channel" or # a "collect: I/O error" statement, so we ignore the STARTTLS one # and similarly for a read timeout # file=sfsasl.c, LogLevel>7, LOG_WARNING ( $ThisLine =~ /^STARTTLS: read error=timeout$/ ) or # file=srvsmtp.c, LogLevel>5, LOG_WARNING ( $ThisLine =~ /^STARTTLS=server, error: accept failed=-1, reason=unknown, SSL_error=5, errno=${\Errno::ECONNRESET}, retry=/ ) or # and yet another symptom of a connection shut down (EPIPE refers to "Broken pipe") # file=srvsmtp.c, LogLevel>5, LOG_WARNING ( $ThisLine =~ /^STARTTLS=server, error: accept failed=-1, reason=unknown, SSL_error=5, errno=${\Errno::EPIPE}, retry=/ ) or # the following is a detailed SSL error log (from tlslogerr) # always preceded by a more user-friendly error message # file=srvrsmtp.c, LogLevel>8, LOG_WARNING ( $ThisLine =~ /STARTTLS=(?:\w*): \d*:error:\w{8}:[^:]*:[^:]*:([^:]*):/ ) or # the following is a log message introduced in 8.13.6 # file=sfsasl.c, LogLevel>14, LOG_INFO # tls_retry errors are either transient, or additional log info is issued and parsed ( $ThisLine =~ /^STARTTLS=(server|client|read|write), info: fds=\d+\/\d+, err=\d$/ ) or # Messages from ruleset try_tls, these can be ignored # ruleset=try_tls, arg1=..., relay=..., reject=550 5.7.1 ... do not try TLS with ... ( $ThisLine=~ /^ruleset=try_tls, arg1=(.*?).*?, reject=550.*do not try TLS with.*/ ) or # AUTH offered, but not authenticated # file=sendmail.cf ( $ThisLine =~ /^ruleset=trust_auth, .* reject=550 5\.7\.1 .*\.\.\. not authenticated$/ ) or # file=queue.c, LogLevel>8, LOG_INFO ( $ThisLine =~ /^runqueue: Flushing queue from/ ) or # file=daemon.c, LogLevel>8, LOG_INFO ( $ThisLine =~ /^accepting connections again for daemon / ) or # do we want to count these? # file=srvrsmtp.c, LogLevel>-1, LOG_INFO ( $ThisLine =~ /probable open proxy: / ) or # the following return error is caught in the "to=" statement ( $ThisLine =~ /^makeconnection \(.*\) failed: / ) or # LOG_DEBUG statements # file=queue.c, LogLevel>79, LOG_DEBUG ( $ThisLine =~ /^queueup / ) or # file=queue.c, LogLevel>69, LOG_DEBUG ( $ThisLine =~ /^runqueue .*, pid=\d*, forkflag=\d*$/ ) or # file=queue.c, LogLevel>76, LOG_DEBUG ( $ThisLine =~ /^dowork, pid=\d*$/ ) or # file=queue.c, LogLevel>76, LOG_DEBUG ( $ThisLine =~ /^doworklist, pid=\d*$/ ) or # file=queue.c, LogLevel>19, LOG_DEBUG ( $ThisLine =~ /^locked$/ ) or # file=queue.c, LogLevel>19, LOG_DEBUG ( $ThisLine =~ /^changed$/ ) or # file=queue.c, LogLevel>19, LOG_DEBUG ( $ThisLine =~ /^too young \(.*\)$/ ) or # file=queue.c, LogLevel>93, LOG_DEBUG ( $ThisLine =~ /^assigned id/ ) or # file=queue.c, LogLevel>87, LOG_DEBUG ( $ThisLine =~ /^unlock$/ ) or # file=main.c, LogLevel>78, LOG_DEBUG ( $ThisLine =~ /^finis, pid=\d*$/ ) or # file=main.c, LogLevel>79, LOG_DEBUG ( $ThisLine =~ /^interrupt$/ ) or # file=main.c, LogLevel>93, LOG_DEBUG ( $ThisLine =~ /^disconnect level \d*$/ ) or # file=main.c, LogLevel>71, LOG_DEBUG ( $ThisLine =~ /^in background, pid=\d*$/ ) or # file=util.c, LogLevel>98, LOG_DEBUG ( $ThisLine =~ /^unlink / ) or # file=deliver.c, LogLevel>80, LOG_DEBUG ( $ThisLine =~ /^sendenvelope, flags=0x[0-9a-fA-F]*$/ ) or # file=envelope.c, LogLevel>84, LOG_DEBUG ( $ThisLine =~ /^dropenvelope, e_flags=0x[0-9a-fA-F]*, OpMode=., pid=\d*$/ ) or # for the following, any return code is still at LogLevel>97, but we only # check for ENOENT return codes, as others are maybe worth looking into # file=queue.c, LogLevel>97, LOG_DEBUG ( $ThisLine =~ /$QueueIDFormat: unlink-fail $ENOENT/o ) or # dumpfd() output ( $ThisLine =~ /\d+: fl=0x\d+, mode=\d+/ ) or # generic DEBUG statement ( $ThisLine =~ /^DEBUG: / ) ) { # We don't care about these statements above # file=srvrsmtp.c } elsif ( ($RelayHost) = ($ThisLine =~ /^--- 250[ -].* Hello (.*), pleased to meet you$/) ) { # record the host for errors on SMTP commands $Msgs{$QueueID}{"Relay"} = $RelayHost; $Msgs{$QueueID}{"BadRCPT"} = 0; # file=headers.c, LogLevel>-1, LOG_INFO } elsif ( ($FromUser, $Bytes, $NumRcpts, $RelayHost) = ($ThisLine =~ /^from=(.*?), .*size=([0-9]+),.*nrcpts=([0-9]+).*relay=(.*)/) ) { if ($NumRcpts > 0) { $MsgsSent++; $AddrRcpts += $NumRcpts; $BytesTransferred += $Bytes; $MailBomber{$RelayHost} += $NumRcpts; $MailBomberConn{$RelayHost}++; if ($Bytes <= 10240) { $SizeDist[0]{'Num'}++; $SizeDist[0]{'Bytes'} += $Bytes; } elsif ($Bytes <= 20480) { $SizeDist[1]{'Num'}++; $SizeDist[1]{'Bytes'} += $Bytes; } elsif ($Bytes <= 51200) { $SizeDist[2]{'Num'}++; $SizeDist[2]{'Bytes'} += $Bytes; } elsif ($Bytes <= 102400) { $SizeDist[3]{'Num'}++; $SizeDist[3]{'Bytes'} += $Bytes; } elsif ($Bytes <= 512000) { $SizeDist[4]{'Num'}++; $SizeDist[4]{'Bytes'} += $Bytes; } elsif ($Bytes <= 1048576) { $SizeDist[5]{'Num'}++; $SizeDist[5]{'Bytes'} += $Bytes; } elsif ($Bytes <= 2097152) { $SizeDist[6]{'Num'}++; $SizeDist[6]{'Bytes'} += $Bytes; } elsif ($Bytes <= 5242880) { $SizeDist[7]{'Num'}++; $SizeDist[7]{'Bytes'} += $Bytes; } elsif ($Bytes <= 10485760) { $SizeDist[8]{'Num'}++; $SizeDist[8]{'Bytes'} += $Bytes; } else { $SizeDist[9]{'Num'}++; $SizeDist[9]{'Bytes'} += $Bytes; } } else { $MsgsNoRcpt++; } # Add info from message to a hash $Msgs{$QueueID}{"Relay"} = $RelayHost; $Msgs{$QueueID}{"FromUser"} = $FromUser; $Msgs{$QueueID}{"Size"} = $Bytes; # file=deliver.c, LogLevel>-1, LOG_INFO } elsif ( ($ToUser, $MailerString, $DeliverStat) = ($ThisLine =~ /^to=(.*?), (.*)stat=(.*)/ ) ) { if ( $DeliverStat =~ /^Sent/ ) { ( ($MailerType) = ( $MailerString =~ /mailer=(.*?),/)); ( ($RelayName) = ( $MailerString =~ /relay=(.*?),/)); if (not defined $MailerType) { $MailerType = "(unspecified)"; } $MailerType =~ s/^\s*$/\(unspecified\)/; # remove the entries from MSP (Mail Submission Program) relay to # localhost if (($MailerType =~ /^relay$/) && (defined $RelayName) && ($RelayName =~ /\[127\.0\.0\.1\]/)) { $RelayLocalhost++; } else { $Mailers{$MailerType}++; } # if $MailerType !~ /^relay$/ ... #This the Top X Email Addresses seen matching -mgt #Build address hash my $CleanTo = $ToUser; $CleanTo =~ s/\//g; $CleanTo =~ s/\"[\w\s]+\"\s?//g; $CleanTo =~ tr/A-Z/a-z/; if (($CleanTo =~ m/\w+\@.+\,\w+/) && (defined $RelayName) && ($RelayName !~ m/\[127\.0\.0\.1\]/)) { my @CleanList = split(/,/, $CleanTo); for my $ListAddr (@CleanList) { $ToList{$ListAddr}++; } } elsif (($CleanTo =~ m/\w+\@[\w\.]+/) && (defined $RelayName) && ($RelayName !~ m/\[127\.0\.0\.1\]/)) { $ToList{$CleanTo}++; } elsif ($CleanTo =~ m/\w+/) { # Match a simple name $ToList{$CleanTo}++; } #Else ignore it if (defined $Msgs{$QueueID}{"Size"}) { if ($Msgs{$QueueID}{"Size"} > 5242880) { #10485760 $LargeMsgs{$Msgs{$QueueID}{"FromUser"} . " \-\> " .$ToUser}++; } # if size > 5242880 } # if defined } elsif ( $DeliverStat =~ /^queued$/ ) { # do nothing if being queued } elsif ( ($Reason) = ( $DeliverStat =~ /^Deferred: (.*)/ ) ) { $StatDeferred{$Reason}{$ToUser}++; } elsif ( $DeliverStat =~ /^Please try again later$/ ) { $StatDeferred{"Milter"}{$ToUser}++; } elsif ( ($Reason) = ( $DeliverStat =~ /(.*)/ ) ) { $StatRejected{$Reason}{$ToUser}++; $StatRejectedLog{$Reason}{$QueueID}++; } } elsif ( ($NewQueueID, $Reason) = ( $ThisLine =~ /^($QueueIDFormat): (?:return to sender|sender notify|postmaster notify|DSN): (.*)/o )) { if (defined $StatRejectedLog{$Reason}{$QueueID}) { # this is a type of error that has been logged, but it is creating a new message $Msgs{$NewQueueID}{"Relay"} = $Msgs{$QueueID}{"Relay"}; $Msgs{$NewQueueID}{"Size"} = $Msgs{$QueueID}{"Size"}; $Msgs{$NewQueueID}{"FromUser"} = "system_notify"; } elsif ($Reason =~ /^Unable to deliver mail$/) { $StatRejected{"Unable to deliver mail"}{"system notify"}++; # Return Receipts from successful delivery } elsif ($Reason =~ /^Return receipt$/) { if (not defined $Msgs{$QueueID}{"FromUser"}) { # The most likely reason for this condition is that the # original email, which identifies the sender, was received # before the --range period specified. $ReturnReceipts{"(Unknown Sender)"}++; } else { $ReturnReceipts{$Msgs{$QueueID}{"FromUser"}}++; } # Timeouts } elsif ($Reason =~ /^(Warning: could not send message for past .*)/ ) { $SentTimeouts{$Reason}++; } elsif ($Reason =~ /^(Cannot send message for .*)/ ) { $SentTimeouts{$Reason}++; } # These are transient errors } elsif (($Reason) = ($ThisLine =~ /^Milter: (?:data|to=.*|from=.*), reject=4\d\d (?:\d\.\d\.\d )?(.*)/) ) { $MilterDeferrals{$Reason}++; # file=deliver.c, LogLevel>4, LOG_INFO } elsif ( ($NewQueueID, $Owner) = ( $ThisLine =~ /($QueueIDFormat): clone: owner=(.*)/o ) ) { $Msgs{$NewQueueID}{"FromUser"} = $Owner; # file=deliver.c, LogLevel>4, LOG_INFO (versions 8.11 and earlier) } elsif ( ($NewQueueID, $Owner) = ( $ThisLine =~ /^clone ($QueueIDFormat), owner=(.*)/o ) ) { $Msgs{$NewQueueID}{"FromUser"} = $Owner; # file=main.c, LogLevel>-1, LOG_INFO } elsif ( $ThisLine =~ /^starting daemon/) { $SendmailStarts++; $SendmailStopped = 0; # file=daemon.c, LogLevel>3, LOG_INFO } elsif ( $ThisLine =~ /^restarting .* due to/) { $SendmailStarts++; $SendmailStopped = 0; # file=daemon.c, LogLevel>9, LOG_INFO } elsif ( $ThisLine =~ /^stopping daemon, reason=/ ) { $SendmailStopped = 1; # After some testing this was removed and EOM is ignored -mgt # file=collect.c, LogLevel>1, LOG_WARNING #} elsif ( ($Reason) = ($ThisLine =~ /^collect: premature EOM: (.*)/) ) { # if (defined $Msgs{$QueueID}{"Relay"}) { # $Source = "From " . $Msgs{$QueueID}{"Relay"}; # } else { # $Source = "Processing $QueueID"; # } # $CollectError{$Reason}{$Source}++; # file=collect.c, LogLevel>0, LOG_NOTICE } elsif ( ($Reason, $Source) = ($ThisLine =~ /collect: (unexpected close|I\/O error|read timeout) on connection from (.*)?, /) ) { $CollectError{$Reason}{$Source}++; # file=collect.c, LogLevel>0, LOG_NOTICE } elsif ( ($Source, $Reason) = ($ThisLine =~ /collect: relay=(.*), from=.*, info=(.*), where=/) ) { $CollectError{$Reason}{$Source}++; # file=collect.c, LogLevel>6, LOG_NOTICE } elsif (($Size) = ($ThisLine =~ /^message size \(([0-9]+)\) exceeds maximum/)) { $OverSize++; $OverSizeBytes += $Size; # file: sendmail.cf } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=([^,]*), relay=[^,]*, reject=550\s*[\d.]*\s*[^ ]*\.\.\. Mailbox disabled for this recipient/) ) { $DisabledMailbox{$User}{$QueueID}++; $Msgs{$QueueID}{"BadRCPT"}++; # test for unknown relay users (users we would have relayed elsewhere) # file: sendmail.cf } elsif ( ($User) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=(.*?), .*\.\.\. User unknown$/) ) { $UnknownUsersCheckRcpt{$User}{$QueueID}++; $Msgs{$QueueID}{"BadRCPT"}++; } elsif ( ($User) = ($ThisLine =~ /^(.*)\.\.\. (User unknown|No such user( here)?)$/i) ) { $UnknownUsers{lc $User}{$QueueID}++; $Msgs{$QueueID}{"BadRCPT"}++; # file: sendmail.cf } elsif ($ThisLine =~ /^--- 553 5\.1\.3 .* User address required$/) { $UnknownUsers{"(unspecified)"}{$QueueID}++; # file: sendmail.cf } elsif ( ($Dest,$Relay) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=([^,]*), relay=([^,]*)(?: \(may be forged\))?, reject=550\s*[\d.]*\s*.*\.\.\. Relaying denied/) ) { $RelayDenied{$Relay}{$Dest}++; $Msgs{$QueueID}{"BadRCPT"}++; } elsif ( ($Auth) = ($ThisLine =~ /^--- 504 5\.3\.3 AUTH mechanism (.*) not available$/) ) { $BadAuth{$Auth}++; # file=sendmail.cf #} elsif ( ($User) = ($ThisLine =~ /^--- 553 5(?:\.\d){2} (.*)\.\.\. Hostname required$/) ) { } elsif ( ($User) = ($ThisLine =~ /^(.*)\.\.\. Hostname required$/) ) { if (not defined $Msgs{$QueueID}{"Relay"}) { # most likely a split envelope. We check for previous QueueId of same timestamp # and process number, with previous sequence number in index 6 and 7. my $QueueIdChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $QueueIdChars .= "abcdefghijklmnopqrstuvwxyz"; my $CharIndex0 = index($QueueIdChars,substr($QueueID, 6, 1)); my $CharIndex1 = index($QueueIdChars,substr($QueueID, 7, 1)); $CharIndex1--; if ($CharIndex1 < 0) { $CharIndex1 = length($QueueIdChars) - 1; $CharIndex0--; if ($CharIndex0 < 0) { $CharIndex0 = length($QueueIdChars) - 1; } } my $OldQueueID = substr($QueueID, 0, 6) . substr($QueueIdChars, $CharIndex0, 1) . substr($QueueIdChars, $CharIndex1, 1) . substr($QueueID, 8); if (defined $Msgs{$OldQueueID}{"Relay"}) { # Previous QueueID found for same host $DomainErrors{$Msgs{$OldQueueID}{"Relay"}}{$User}++; } else { # Unknown host $DomainErrors{"(Missing hostname)"}{$User}++; } } else { # No split envelope; we already have hostname $DomainErrors{$Msgs{$QueueID}{"Relay"}}{$User}++; } # file: sendmail.cf } elsif ( ($User, $RelayHost) = ($ThisLine =~ /^ruleset=check_(?:mail|rcpt), arg1=(.*), relay=(.*), reject=451\s*[\d.]*\s*Domain of sender address .* does not resolve/) ) { # I don't think we should include this, because it is a temporary error # $DomainErrors{$RelayHost}{$User . ": (does not resolve)"}++; # file: sendmail.cf } elsif ( ($RelayHost,$User) = ($ThisLine =~ /^ruleset=check_(?:mail|rcpt), arg1=.*, relay=(.*), reject=553\s*[\d.]*\s*.*\.\.\. Domain of sender address (.*) does not exist/) ) { $User =~ s/^.+\@(.+)$/$1/ if ($Detail < 15); $DomainErrors{$RelayHost}{$User . " (sender does not exist)"}++; $Msgs{$QueueID}{"BadRCPT"}++; # file: sendmail.cf } elsif ( ($User,$RelayHost) = ($ThisLine =~ /^ruleset=check_mail, arg1=(.*), relay=(.*), reject=553\s*[\d.]*\s*.*\.\.\. Domain name required for sender address .*/) ) { $DomainErrors{$RelayHost}{$User . " (sender domain missing)"}++; # file: sendmail.cf NOT STOCK moved for order detection reasons -mgt } elsif ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=([^,]*),(?: arg2=[^,]*,)?(?: relay=[^,]*,)? reject=55\d\s*[\d.]*\s*.*(?:Mail from|Rejected:) [^ ]* (?:refused by blackhole site|listed at|found in) (.*)/) { $Temp = "From " . $1 . " by " . $2; $BlackHoled{$Temp}++; $BlackHoles{$2}++; } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=[^,]*,(?: arg2=[^,]*,)? relay=([^,]*), reject=55\d\s*[\d.]*\s*.*http:\/\/([^\/\s]*)/) ) { $Temp = "From " . $Relay . " by " . $BlSite; $BlackHoled{$Temp}++; $BlackHoles{$BlSite}++; } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /^ruleset=(?:check_relay|check_rcpt), arg1=([^,]*),(?: arg2=[^,]*,)? reject=55\d\s*[\d.]*\s*.*http:\/\/([^\/\s]*)/) ) { #Example 553 error with NO RELAY -mgt #ruleset=check_relay, arg1=s010600402b39ee29.vf.shawcable.net, arg2=127.0.0.2, reject=553 5.3.0 #Spam blocked see: http://spamcop.net/bl.shtml?70.68.8.182: 1 Time(s) $Temp = "From " . $Relay . " by " . $BlSite; $BlackHoled{$Temp}++; $BlackHoles{$BlSite}++; } elsif ( ($Relay,$BlSite) = ($ThisLine =~ /reject=553\s*[\d.]*\s*<[^ ]*>\.\.\. +Mail from ([\d\.]+) rejected\;see http:\/\/([^\/\s]*)/) ) { #This is the another blackhole tag -mgt $Temp = "From " . $Relay . " by " . $BlSite; $BlackHoled{$Temp}++; $BlackHoles{$BlSite}++; } elsif ( ($BlSite, $Relay) = ($ThisLine =~ /reject=553\s*[\d.]*\s*<[^ ]*>\.\.\. +Email blocked using ORDB.org - see \\.\.\. Mail from [^ ]* refused by blackhole site ([^ ]*)/) ) { # $Temp = "From " . $Relay . " by " . $BlSite; # $BlackHoled{$Temp}++; # $BlackHoles{$BlSite}++; # $Msgs{$QueueID}{"BadRCPT"}++; # test for all kinds of rejects due to check_mail # file: sendmail.cf } elsif( ($Arg,$Relay,$Reason) = ($ThisLine =~ /^ruleset=check_mail, arg1=(.*), relay=.*?\[(.*)\].*, reject=(.*)/) ) { $Temp = "[$Relay] $Arg\n\t$Reason"; $CheckMailReject{$Temp}++; # file: sendmail.cf } elsif( ($Arg,$Relay,$Reason) = ($ThisLine =~ /^ruleset=check_rcpt, arg1=(.*), relay=.*?\[(.*)\].*, reject=(.*)/) ) { $Reason =~ s/\Q$Arg\E\.\.\. //; $Temp = "$Arg ($Reason)"; $CheckRcptReject{$Temp}++; $Msgs{$QueueID}{"BadRCPT"}++; # file=srvrsmtp.c, LogLevel>1, LOG_NOTICE } elsif ( ($Temp) = ($ThisLine =~ /^lost input channel from (.*) to .* after .*/) ) { $LostInputChannel{$Temp}++; # file=collect.c, LogLevel>2, LOG_NOTICE # file=control.c, LogLevel>2, LOG_NOTICE # file=util.c, LogLevel>1, LOG_NOTICE } elsif ( ($Temp) = ($ThisLine =~ /^timeout waiting for input (from \S+|during control command)/) ) { $Timeouts{$Temp}++; # file=milter.c, LogLevel>10, LOG_INFO } elsif ( $ThisLine =~ /Milter: no active filter/) { $NoMilterFilters++; # file=srvrsmtp.c } elsif ( ($Temp) = ($ThisLine=~ /\-\-\- 500 5\.5\.1 Command unrecognized: \"([^\"]*)/) ) { chomp($Temp); # And we write it like the incoming SMTP command, so we can later # check it against unmatched entries ($OtherList). $Temp1 = "<-- $Temp"; # We normalize tab, newline, and return to use their octal values, # as sendmail converts escaped octals. $Temp1 =~ s/\\t/\\011/g; $Temp1 =~ s/\\n/\\012/g; $Temp1 =~ s/\\r/\\015/g; # Remove non-printable space(s) at the end $Temp1 =~ s/\s*$//; # we try to delete it from the list of Unmatched Entries if (defined $OtherList{$Temp1}) { $OtherListFound = 1; if ($OtherList{$Temp1} == 1) { delete ($OtherList{$Temp1}); } elsif ($OtherList{$Temp1} > 1) { $OtherList{$Temp1}--; } else { # This should not happen. So we punt it to the # OtherList to be printed at the end. $OtherList{"Command unrecognized: " . $Temp}++; } } else { $OtherListFound = 0; $OtherList{$Temp1}++; } # Ignore commands from connects that failed greeting if (not defined $PREGreetingQueue{$QueueID}) { if ($Temp =~ /^$/) { $Temp = ""}; if ($OtherListFound == 0) { if (not defined $CommandUnrecognized{$QueueID}) { # initialize string, as we will concatenate commands $CommandUnrecognized{$QueueID} = ""; } $CommandUnrecognized{$QueueID} .= "\t" . $Temp . "\n"; } } # similarly, delete last unmatched entry when too many bad commands } elsif ( $ThisLine =~ /^--- 421 4\.\d\.\d .* Too many bad commands; closing connection$/) { if (defined $OtherList{$LastCmd{$QueueID}}) { delete ($OtherList{$LastCmd{$QueueID}}); } # file=srvrsmtp.c, LogLevel>9, LOG_INFO } elsif ( ( $User, $Host ) = $ThisLine =~ /^invalid domain name \((.*)\) from (.*)/ ) { $DomainErrors{$Host}{$User . " (invalid domain name)"}++; # file=srvrsmtp.c, LogLevel>5, LOG_INFO } elsif ( ( $Host ) = ($ThisLine =~ /(.*) (\(may be forged\) )?did not issue MAIL\/EXPN\/VRFY\/ETRN during connection to /) ) { # we test if they previously sent junk, because the connection is expected to fail if (defined $CommandUnrecognized{$QueueID}) { $CommandUnrecognized{$QueueID} .= " ... and then exited without communicating\n"; } else { $DummyConnection{$Host}++; } # file=srvrsmtp.c, LogLevel>-1, LOG_INFO } elsif ($ThisLine =~ /rejecting commands from (.*) due to pre-greeting traffic/ ) { $PREGreeting{$1}++; $PREGreetingQueue{$QueueID}++; } elsif ($ThisLine =~ /rejecting server EHLO from (.*) due to traffic before response/ ){ $PREGreeting{$1}++; $PREGreetingQueue{$QueueID}++; # possible "(may be forged)" after IP address # file=srvrsmtp.c, LogLevel>5, LOG_INFO } elsif ( ($Temp) = ($ThisLine =~ /^.*\[(.*?)\].*: Possible SMTP RCPT flood, throttling./) ) { $BadRcptThrottle{$Temp}++; # file=srvrsmtp.c } elsif ($ThisLine =~ /^Too many recipients$/) { $TooManyRcpts++; # file=deliver.c (note: while this is a syserr, I think it's reasonable to extract it here, as there is not # much the sender can do if they don't own the recipient address } elsif ( ($Temp) = ($ThisLine =~ /^.*?Too many hops (.*)/) ) { $TooManyHops{$Temp}++; # file=main.c LogLevel>3, LOG_INFO } elsif ( ($Warning) = ($ThisLine =~ /Authentication-Warning: (.*)/) ) { $AuthWarns{$Warning}++; # file=alias.c, LogLevel>2, LOG_ERR } elsif ( ($Forward,$Error) = ($ThisLine =~ /^forward ([^ ]*): transient error: (.*)/) ) { $Temp = $Forward . ": " . $Error; $ForwardErrors{$Temp}++; # file=alias.c, LogLevel>2,10, LOG_WARNING } elsif ( ($Forward,$Error) = ($ThisLine =~ /^forward ([^ ]*): (.*)/) ) { $Temp = $Forward . ": " . $Error; $ForwardErrors{$Temp}++; # file=collect.c, LogLevel>-1, LOG_NOTICE } elsif ($ThisLine=~ /^headers too large .* from (.*) during message collect$/) { $LargeHdrs{$1}++; # file=srvrsmtp.c, LogLevel>5, LOG_INFO } elsif ($ThisLine=~ /(\S*) ?\[(IPv6:)?([0-9A-F\.:]+)\](?: \(may be forged\))?: (\S+) (\S+) \[rejected\]/i) { chomp($Host=$3." ". (defined($1) ? "(".$1.")" : "(unresolved)") ); $Luser=$5; $RejCmd=uc $4; $Abuse{$Host}{$Luser}{$RejCmd}++; # file=srvrsmtp.c, LogLevel>5, LOG_INFO } elsif ( $ThisLine =~ /\[(IPv6:)?([0-9A-F\.:]+)]: ETRN (\S+)/i ) { chomp($ETRN=$3." from ".$2); $ETRNs{$ETRN}++; # file=conf.c, LogLevel>8, LOG_NOTICE } elsif ( $ThisLine =~ /rejecting connections on daemon [^ ]+: load average: ([0-9]+)/ ) { $LoadAvg{$1}++; $LoadAvgReject++; # file=conf.c, LogLevel>8, LOG_INFO } elsif ( ($Reason) = ($ThisLine =~ /(deferring connections on daemon .*): \d+ per second/) ) { $RuleSets{$Reason}++; # file=conf.c, LogLevel>3, LOG_NOTICE } elsif ($ThisLine=~ /tcpwrappers \((.+), (.+)\)/) { chomp($Host=$2); $MailRejected{$Host}++; } elsif ( # file=queue.c, LogLevel>8, LOG_INFO ($ThisLine =~ /Aborting queue run: load average too high/ ) or # file=queue.c, LogLevel>8, LOG_INFO ($ThisLine =~ /Skipping queue run -- load average too high/ ) ){ $LoadAvgQueueSkip++; # file=stats.c, LogLevel>12, LOG_INFO } elsif ( ($StatFile, $StatError) = ($ThisLine=~ /^poststats: (.*?): (.*)/) ) { $StatFileError{$StatFile}{$StatError}++; # file=srvrsmtp.c, LogLevel>9, LOG_WARNING } elsif ( ($Auth, $Reason, $DetailReason, $RelayHost) = ($ThisLine =~ /^AUTH failure \((.*)?\): ([^\(]*)(\(.*) relay=(.*)/) ) { if ( $Reason =~ /^user not found / ) { (($User) = ($DetailReason =~ /\(-20\) SASL\(-13\): user not found: .*user(?:=|: )([^ ,]*)/)); $AUTHnouser{$User}++; } else { $AUTHfailure{$RelayHost}{$Reason}++; } # file=tls.c, LogLevel>7, LOG_INFO } elsif ($ThisLine=~ /STARTTLS=.* field=cn_issuer, status=failed to extract CN/ ) { $NoCommonName++; # file=tls.c, LogLevel>12, LOG_WARNING } elsif ( ($TLSFile) = ($ThisLine=~ /STARTTLS: (.* missing)/) ) { $TLSFileMissing{$TLSFile}++; # file=tls.c, LogLevel>7, LOG_WARNING } elsif ( ($TLSFile) = ($ThisLine=~ /STARTTLS=((server|client): file .* unsafe: .*)/) ) { $TLSFileMissing{$TLSFile}++; # file=srvrsmtp.c, LogLevel>5, LOG_WARNING; reason given as of 8.14.6 } elsif ( ($TLSReason, $TLSFrom) = ($ThisLine=~ /STARTTLS=server, error: accept failed=-?\d+, reason=([^,]*), (?:[^,]*,){3} relay=(.*)/) ) { $TLSAcceptFailed{$TLSReason}{$TLSFrom}++; # handle pre-8.14.6 } elsif ( ($TLSFrom) = ($ThisLine=~ /STARTTLS=server, error: accept failed=-?\d+, SSL_error=(?:[^,]*,){3} relay=(.*)/) ) { $TLSAcceptFailed{"no reason given"}{$TLSFrom}++; # file=deliver.c, LogLevel>5, LOG_WARNING; reason given as of 8.14.6 } elsif ( ($TLSReason) = ($ThisLine=~ /STARTTLS=client, error: connect failed=-?\d+. reason=([^,]*),/) ) { $TLSConnectFailed{$TLSReason}++; # handle pre-8.14.6 } elsif ($ThisLine=~ /STARTTLS=client, error: connect failed=-?\d+. SSL_error=/) { $TLSConnectFailed{"no reason given"}++; # file=tls.c, LogLevel>-1, LOG_INFO } elsif (($CommonName,$StarttlsReason) = ($ThisLine =~ /^STARTTLS: (?:x509|TLS) cert verify: depth=[0-9]+ .*\/CN=([^\/,]*).* state=[0-9]+, reason=(.*)$/ )) { # "reason=ok" now printing in sendmail 8.16.1; not an error if ($StarttlsReason !~ /^ok$/) { $StarttlsCert{$StarttlsReason}{$CommonName}++; } # do the same if, incorrectly, Common Name is not defined } elsif (($StarttlsReason) = ($ThisLine =~ /^STARTTLS: (?:x509|TLS) cert verify: depth=[0-9]+ .* state=[0-9]+, reason=(.*)$/ )) { # "reason=ok" now printing in sendmail 8.16.1; not an error if ($StarttlsReason !~ /^ok$/) { $StarttlsCert{$StarttlsReason}{"(undefined CommonName)"}++; } # file=tls.c, LogLevel>8, LOG_INFO } elsif ( ($StarttlsMode, $StarttlsVerify, $StarttlsCipherType, $StarttlsNumBits) = ($ThisLine =~ /^STARTTLS=(server|client), relay=.*, version=.*, verify=(\w*), cipher=(.*), bits=(\w*\/\w*)/) ) { if ($StarttlsVerify =~ /^OK$|^TEMP$|^PROTOCOL$|^SOFTWARE$|^NO$|^NOT$|^FAIL$|^NONE$/) { $Starttls{$StarttlsMode}{$StarttlsVerify}++; } else { $Starttls{$StarttlsMode}{'Other'}++; } $StarttlsCipher{"Cipher: " . $StarttlsCipherType . " Bits: " . $StarttlsNumBits}++; # We capture only certain STARTTLS errors. Currently, only EPIPE (broken pipe) and # ECONNRESET (connection reset by peer). # Not strictly a STARTTLS error, but it happens when a client ungracefully # exits during a STARTTLS exchange, often after actually delivering mail. # But we make a note of it, anyway. Unfortunately, there is no QueueID. # We might add more errnos in the future, but need to make sure they are not # due to a problem that can be resolved. # file=sfsasl.c, LogLevel>7, LOG_WARNING } elsif ( ($Errno) = ($ThisLine =~ /^STARTTLS: (?:read|write) error=syscall error \(-1\), errno=(${\Errno::EPIPE}|${\Errno::ECONNRESET}|${\Errno::ETIMEDOUT}), / )) { $TLSErrno{$Errno}++; # file=queue.c, LogLevel>-1, LOG_ALERT } elsif ( ($Reason) = ($ThisLine=~ /^Losing (.*)/ ) ) { $LostQueueFile{$Reason}++; # file=queue.c, LogLevel>0, LOG_ALERT } elsif ( ($File) = ($ThisLine=~ /^low on space \(.* in (.*)\), max avail/ ) ) { $LowSpace{$File}++; # file=daemon.c, LogLevel>8, LOG_INFO } elsif ($ThisLine=~ /^rejecting new messages/) { $DaemonThrottle++; # this appears to be the result of EX_PROTOCOL return code, so it should be handled with other EX_ messages } elsif ($ThisLine=~ /Remote protocol error/) { $RemoteProtocolError++; } elsif ( # file=util.c, LogLevel>-1, LOG_NOTICE (($Host,$Attack) = ($ThisLine =~ /POSSIBLE ATTACK from ([^ ]+): (.*)/)) or # fqdn may be missing before IP address # file=srvrsmtp.c, LogLevel>5, LOG_INFO (($Host,$Attack) = ($ThisLine =~ /(.*\[[^ ]+\])(?:\s+\(may be forged\))?: possible SMTP attack: (.*)$/)) ) { $AttackAttempt{$Host}{$Attack}++; #file=headers.c, LogLevel>-1, LOG_ALERT } elsif (($Attack) = ($ThisLine =~ /^(.*) \(possible attack\)$/)) { $AttackAttempt{"UNKNOWN"}{$Attack}++; # file=usersmtp.c, LogLevel>8, LOG_WARNING } elsif ( ($File,$Error) = ($ThisLine =~ /error: safesasl\(([^ ]+)\) failed: (.*)$/) ) { $SaslError{$File}{$Error}++; # can't find the following } elsif ( $ThisLine =~ /Can\'t create output/ ) { $CantCreateOutput++; # file=alias.c, LogLevel>3, LOG_INFO } elsif ( $ThisLine =~ /alias database [^ ]+ out of date/ ) { $OutdatedAliasdb++; # We'll filter the "No space left" error because they tend to manifest # in many different ways # file=err.c, LogLevel>0, LOG_CRIT, LOG_ALERT } elsif ( $ThisLine =~ /No space left on device$/ ) { $NoMoreSpace++; # SYSERR are usually serious... # file=err.c, LogLevel>0, LOG_CRIT, LOG_ALERT } elsif ( ($User,$Reason) = ($ThisLine =~ /SYSERR\((.*)\): (.*)/) ) { $SysErr{$User}{$Reason}++; # file=milter.c, LogLevel>8, LOG_INFO } elsif ( ($HeaderMod) = ($ThisLine =~ /Milter (?:\(\w*\) )?(?:add|insert|change|delete).*: header: (.*)/) ) { foreach $Header (@MilterHeadersToCount) { if ($HeaderMod =~ /$Header/) { $MilterHeaderCount{$Header}++; } } # file=milter.c, LogLevel>3, LOG_INFO } elsif ((my $Milter,$Reason) = ($ThisLine =~ /milter\=(.*), quarantine\=(.*)/)) { my $QuarantineReason = $Milter . ": " . $Reason; $Quarantined{$QuarantineReason}++; } elsif ( # file=parseaddr.c, LogLevel>3, LOG_NOTICE ($Address,$Reason) = ($ThisLine =~ /^Syntax error in mailbox address "(.+)" \(([^ ]+)\)/) or # file=sendmail.cf ($Address,$Reason) = ($ThisLine =~ /^<(.+)>\.\.\. (Colon illegal in host name part)/) or # file=parseaddr.c, LogLevel>3, LOG_NOTICE ($Reason,$Address) = ($ThisLine =~ /^(8-bit character in mailbox address) "<(.+)>"/) ) { $AddressError{$Reason}{$Address}++; } elsif ($ThisLine =~ /ruleset=check_relay, arg1=([^,]*),.* reject=550 5\.7\.1 Access denied/) { # We block some particularly annoying spam domains with the # following in /etc/mail/access... # From:example.com ERROR:550 5.7.1 Access denied # Remember the error message is user defined in /etc/mail/access # So if anyone can make a better check please do -mgt # Note (-bl): the same output is achieved by using the label REJECT in /etc/mail/access file: # From:example.com REJECT $KnownSpammer{$1}++; # add support for DISCARD in /etc/mail/access } elsif ($ThisLine =~ /ruleset=check_(?:mail|rcpt), arg1=([^,]*), relay=.*\[.+\]( \(may be forged\))?, discard/) { $KnownSpammer{$1}++; } elsif ( # file=milter.c, LogLevel>8, LOG_INFO ( $ThisLine =~ /Milter (?:\(\w*\) )?(add|change|insert|delete): /) ) { # We don't care about these statements above # DNS Map lookups: file=map.c, LogLevel>9, LOG_INFO } elsif ($ThisLine=~ /dns (\S+)\. =\> (\d+.\d+.\d+.\d+)/) { chomp($Domain=$1); chomp($IP=$2); $DNSMap{$Domain}{$IP}++; # Here are the statements whose source are not from the stock sendmail: # This is from libspf } elsif ( (my $SPFStatus) = ($ThisLine =~ /^Received-SPF: (fail|softfail|neutral|none|error|unknown|pass) /) ) { $SPFResults{$SPFStatus}++; # This is for the Sendmail Sender-ID milter } elsif ( (my $SenderIDStatus, $SPFStatus) = ($ThisLine =~ /^Milter (?:\(\w*\) )?insert \(1\): header: Authentication-Results:.*; sender-id=(fail.*|softfail|neutral|none|error|unknown|pass); spf=(fail.*|softfail|neutral|none|error|unknown|pass)/) ) { # Example string # Milter insert (1): header: Authentication-Results: my.host.name # sender=list-users-bounces+list-users=host.name@another.org; # sender-id=neutral; spf=neutral $SPFResults{$SPFStatus}++; $SenderIDResults{$SenderIDStatus}++; # file: access #This looks to be a custom ruleset added by Hugo van der Kooij -mgt #Probably would be better renamed as localrule or something. #Google showed me http://hvdkooij.xs4all.nl/email-sendmail.cms } elsif ($ThisLine=~ /ruleset=check_XS4ALL/) { $XS4ALL++; } elsif ( # This one appears to be the result of a file/socket read; it's not clear to me why we want to ignore it ( $ThisLine =~ /Broken pipe|Connection (reset|timed out)/ ) or ( $ThisLine =~ /Milter: from=/ ) ) { # do nothing; statements are filtered out } elsif ($ThisLine =~ /reject=550 5\.7\.1 <[^ ]*@([^ ]*)>\.\.\. Relaying Denied/) { # We block some particularly annoying spam domains with the following in /etc/mail/access... # From:example.com ERROR:550 5.7.1 Relaying Denied (Spammer) # Note (-bl): this is the same as an earlier check_rcpt, except that the word Denied is capitalized here. # So to avoid confusion I suggest that we use the REJECT label in the access file. $KnownSpammer{$1}++; } elsif ( ($Host) = ($ThisLine =~ /relay=([^ ]+ \[[^ ]+\]), reject=553 5\.3\.0 .*/) or ($Host) = ($ThisLine =~ /relay=([^ ]+ \[[^ ]+\] \(may be forged\)), reject=553 5\.3\.0 .*/) ) { $KnownSpammer{$Host}++; } elsif ($ThisLine=~ /relay=(\S+)*.*\[(\d+.\d+.\d+.\d+)\], reject=444 4.4.4 \<([^\>]+)\>\.\.\. Sorry (\S*)/) { chomp($Host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") ); chomp($Luser=$3); chomp($Ruser=$4); $Ruser="none" if (length($Ruser)==0); $RelayReject{$Host}{$Ruser}{$Luser}++; } elsif ($ThisLine=~ /arg1=\<([^\>]+)\>, relay=(\S+)*.*\[([^\]]+)\], reject=444 4.4.4 Sorry (\S*)/) { chomp($Host=$3." ". (defined($2) ? "(".$2.")" : "(unresolved)") ); chomp($Ruser=$1); $Luser="none"; $RelayReject{$Host}{$Ruser}{$Luser}++; } elsif ($ThisLine=~ /relay=(\S+)*.*\[(\d+.\d+.\d+.\d+)\], reject=441 4.4.1 \<([^\>]+)\>/) { chomp($Host=$2." ". (defined($1) ? "(".$1.")" : "(unresolved)") ); chomp($Luser=$3); $NotLocal{$Host}{$Luser}++; } elsif ($ThisLine=~ /reject=.*MESSAGE NOT ACCEPTED - (.+)/) { chomp($Host=$1); $MailRejected{$Host}++; # default for ruleset checks. } elsif ( ($Reason) = ($ThisLine =~ /^ruleset=.*, arg1=.*, reject=(?:\d{3} )?(?:\d\.\d\.\d )?(.*)$/) ) { $RuleSets{$Reason}++; } elsif ($ThisLine=~/Warning: program (.*) unsafe: (.*)/) { chomp($Directory=$1); chomp($Cause=$2); $WUnsafe{$Directory}{$Cause}++; # the following is the catch-all: } elsif ( ($Milter,$Error) = ($ThisLine =~ /^Milter \((.*)\): (.+)/) ) { $MilterErrors{$Milter}{$Error}++; } elsif ( $ThisLine =~ /$QueueIDFormat\[[2-9]\]: / ) { # For very large input command lines (hundreds of # characters), sendmail breaks them down and puts an # index after the QueueID when echoing in debug mode # (level > 14). So we'll process the first one, and # ignore the other ones. } else { $ThisLine =~ s/.*\: (DSN\: .*)/$1/; $ThisLine =~ s/.*\: (postmaster notify\: .*)/$1/; chomp($ThisLine); # Report any unmatched entries... # Remove any whitespace at the end $ThisLine =~ s/\s*$//; # For the very first of the very long debug echo lines, # handle the case of the first indexed QueueID. $ThisLine =~ s/${QueueIDFormat}\[1\]: <-- /<-- /; if ($ThisLine =~ /<--/) { # We got here because these are unmatched entries. Often it # is because a client sends binary code when it shouldn't. # When sendmail reads SMTP commands, it converts these # non-printable characters into escaped sequences, so we do # the same here. # First, we deal with the special case of a string containing # \200, as it becomes a null character, terminating the string. $ThisLine =~ s/\\200(.*)//; $ThisLine =~ s/\\([23]\d{2})/ # clear the most significant bit if set my $tempchar = oct($1 - 200); if ($tempchar == 92) { # backslash is escaped with another backslash sprintf("\\\\"); } elsif (($tempchar >= 32) && ($tempchar != 127)) { # if the new value is a printable ASCII character, print it chr($tempchar); } else { # if not printable ASCII, leave as octal code sprintf("\\%03o", $tempchar); }/eg; # When a string has embedded double-quotes, sendmail stops processing. $ThisLine =~ s/".*//; # We normalize tab, newline, and return to octal values $ThisLine =~ s/\\t/\\011/g; $ThisLine =~ s/\\n/\\012/g; $ThisLine =~ s/\\r/\\015/g; # Remove non-printable space(s) at the end $ThisLine =~ s/\s*$//; } # MAXSHORTSTR defined as 203 in sendmail. But escaped octal characters # need to be counted as single character (my $deoctal = $ThisLine) =~ s/\\\d\d\d/X/g; if (length($deoctal) > 203) { my $shortstring; if ($ThisLine =~ /^<-- /) { # We added the four characters '<-- ' so the resulting beginning of the # string is 100+4 characters $shortstring = 104; } else { $shortstring = 100; } $ThisLine = substr($ThisLine, 0, $shortstring) . '...' . substr($ThisLine, -100); } $OtherList{$ThisLine}++; # Store last unmatched entry, in case it is needed later. But note that some # statements have no QueueID. if (defined $QueueID) { $LastCmd{$QueueID} = $ThisLine; } } } ####################################################### # The following variables are used to print a header # only if there is subsequent data to print for each category my $HeaderPrinted = 0; my $TotalHeaderPrinted = 0; my $CurrentHeader = ""; my $PrintCond = "unless (\$HeaderPrinted) {print \$CurrentHeader; \$HeaderPrinted = 1;}"; $CurrentHeader = "\n\nSEVERE ERRORS\n-------------"; $HeaderPrinted = 0; if ($SendmailStopped) { eval "$PrintCond"; print "\n\nSendmail IS NOT RUNNING!\ If you do not wish to run sendmail, delete the mail log\ file (such as /var/log/maillog) to suppress this message."; } my @TotalSevereError = (); my $SevereErrorIndex = 0; $TotalSevereError[0] = 0; if (keys %SysErr) { eval "$PrintCond"; print "\n\nSystem Error Messages:"; # don't sort, as error order may help foreach $User (keys %SysErr) { foreach $Reason (keys %{$SysErr{$User}}) { PrettyTimes(" $Reason", $SysErr{$User}{$Reason}); $TotalSevereError[$SevereErrorIndex] += $SysErr{$User}{$Reason}; } } } $TotalSevereError[++$SevereErrorIndex] = 0; if (keys %LostQueueFile) { eval "$PrintCond"; print "\n\nLost Queue Files:"; # don't sort and don't list counts (Queue ID is included) foreach $Reason (keys %LostQueueFile) { print "\n $Reason"; $TotalSevereError[$SevereErrorIndex] += $LostQueueFile{$Reason}; } } $TotalSevereError[++$SevereErrorIndex] = 0; if ($NoMoreSpace > 0) { eval "$PrintCond"; print "\n\nError \"No space left on device\" occurred $NoMoreSpace time(s)"; $TotalSevereError[$SevereErrorIndex] += $NoMoreSpace; } $TotalSevereError[++$SevereErrorIndex] = 0; my $TotalCount = 0; foreach $ErrorCount (@TotalSevereError) { if (defined $ErrorCount) { $TotalCount+= $ErrorCount; } } if ($TotalCount > 0) { print "\n\nTotal SEVERE ERRORS: $TotalCount"; } $TotalHeaderPrinted += $HeaderPrinted; $CurrentHeader = "\n\nSENDMAIL CONFIGURATION\n----------------------"; $HeaderPrinted = 0; if (keys %LowSpace) { eval "$PrintCond"; print "\n\nWarning: Low space when writing to:"; foreach $File (keys %LowSpace) { print "\n $File"; } } if ($DaemonThrottle > 0) { eval "$PrintCond"; print "\n\nDaemon started rejecting messages $DaemonThrottle times due to lack of space"; } if (keys %TLSFileMissing) { eval "$PrintCond"; print "\n\nWarning: STARTTLS file errors:"; foreach $TLSFile (sort keys %TLSFileMissing) { print "\n $TLSFile"; } } if (keys %StatFileError) { eval "$PrintCond"; print "\n\nWarning: Error opening statistics files:"; foreach $StatFile (sort keys %StatFileError) { print "\n $StatFile:"; foreach $StatError (keys %{$StatFileError{$StatFile}}) { print "\n $StatError"; } } } if ($NoMilterFilters > 0) { eval "$PrintCond"; print "\n\nNo active milter filters"; } if ($OutdatedAliasdb > 0) { eval "$PrintCond"; print "\n"; PrettyTimes("Aliases database out of date", $OutdatedAliasdb); } if (keys %WUnsafe) { print "\n\nUnsafe permissions:\n"; foreach $Directory (keys %WUnsafe) { print " In program " . $Directory . ":"; foreach $Cause (keys %{$WUnsafe{$Directory}}) { PrettyTimes(" $Cause", $WUnsafe{$Directory}{$Cause}); } } } if (keys %SaslError) { eval "$PrintCond"; print "\n\nSASL database Errors:\n"; foreach $File (sort {$a cmp $b} keys %SaslError) { print " In file $File:"; foreach $Error (sort {$a cmp $b} keys %{$SaslError{$File}}) { PrettyTimes(" $Error", $SaslError{$File}{$Error}); } } } if (keys %TooManyHops) { eval "$PrintCond"; print "\n\nToo many hops:"; foreach $ThisOne (sort keys %TooManyHops) { PrettyTimes(" $ThisOne", $TooManyHops{$ThisOne}); } } $TotalHeaderPrinted += $HeaderPrinted; $CurrentHeader = "\n\nSTATISTICS\n----------"; $HeaderPrinted = 0; if (($SendmailStarts or $BytesTransferred or $MsgsSent or $AddrRcpts) and ($Detail >= 1)) { eval "$PrintCond"; if ($SendmailStarts > 0) { print "\n\nSendmail was started $SendmailStarts time(s)"; } printf("\n\nMessages To Recipients:%11d", $MsgsSent); # Each explicitly addressed recipient in an email is counted as an # "Addressed Recipient" printf("\nAddressed Recipients:%13d", $AddrRcpts); printf("\nBytes Transferred:%16d", $BytesTransferred); # Messages with no valid recipients (for example, fails # some other check) are not delivered printf("\nMessages No Valid Rcpts:%10d", $MsgsNoRcpt); } if (($Detail >= 10) and $HeaderPrinted) { # eval $PrintCond not needed, because tested for $HeaderPrinted print "\n\nMessage Size Distribution:\n"; print "Range # Msgs KBytes\n"; $TotalNum = 0; # Initialise the Size distribution array my @SizeNames = ('0 - 10k', '10k - 20k', '20k - 50k', '50k - 100k', '100k - 500k', '500k - 1Mb', '1Mb - 2Mb', '2Mb - 5Mb', '5Mb - 10Mb', '10Mb+'); foreach $LastIndex (0..9) { $LastIndex2=9-$LastIndex; last if ($SizeDist[$LastIndex2]{'Bytes'} > 0); } foreach $ThisOne (0..$LastIndex2) { printf("%-12s %6d %10d\n", $SizeNames[$ThisOne], $SizeDist[$ThisOne]{'Num'}, $SizeDist[$ThisOne]{'Bytes'}/1024); $TotalNum += $SizeDist[$ThisOne]{'Num'}; $TotalBytes += $SizeDist[$ThisOne]{'Bytes'}; } print "----------------------------------\n"; printf("TOTAL %6d %10d\n", $TotalNum, $TotalBytes/1024); if ($TotalNum > 0) { printf("Avg. Size %10d\n", ($TotalBytes / $TotalNum)/1024); } } if (($Detail >= 5) and (keys %LargeMsgs)) { eval "$PrintCond"; print "\n\nLarge Messages (From \-\> To):"; foreach $ThisOne (sort keys %LargeMsgs) { PrettyTimes(" $ThisOne", ${LargeMsgs{$ThisOne}}); } } # Message recipients are the actual recipients - one for each # recipient to which to which a copy of email is sent if (($Detail >= 10) and (keys %Mailers)) { eval "$PrintCond"; my @DefinedMailers = ('smtp', 'esmtp', 'smtp8', 'dsmtp', 'relay', 'procmail', 'local', 'prog', '*file*'); print "\n\nMessage recipients per delivery agent:"; $TotalNum = 0; print "\nName # Rcpts"; # first we print the common mailers, to maintains some logical grouping foreach $MailerName (@DefinedMailers) { if ($Mailers{$MailerName}) { printf("\n%-12s %6d", $MailerName, $Mailers{$MailerName}); $TotalNum += $Mailers{$MailerName}; delete($Mailers{$MailerName}); } } # now we print all remaining mailers foreach $MailerName (keys %Mailers) { printf("\n%-12s %6d", $MailerName, $Mailers{$MailerName}); $TotalNum += $Mailers{$MailerName}; } printf("\n---------------------\nTOTAL: %6d", $TotalNum); if ($RelayLocalhost > 0) { printf("\nin addition to %6d relay", $RelayLocalhost); print "\n submission\(s\) from MSP"; } } if (($Detail >= 10) && (keys %ToList)) { eval "$PrintCond"; my $ToListCount = 0; #Set default -mgt my $ToListThreshold = $ENV{'sendmail_tolistthreshold'} || "10"; #Set sendmail_tolistthreshold = Null to suppress this report -mgt if ($ToListThreshold !~ m/NULL/i) { print "\n\nTop $ToListThreshold Email Recipients\n"; foreach my $ToAddr (sort {$ToList{$b}<=>$ToList{$a}} keys %ToList) { if ($ToListCount >= $ToListThreshold) { last; }; PrettyTimes(" " . $ToAddr, $ToList{$ToAddr}); $ToListCount++; } } } if (($Detail>=10) and (keys %MailBomber)) { eval "$PrintCond"; my $MailBombCount = 0; #Set up are defaults -mgt my $MailbombListThreshold = $ENV{'sendmail_mailbomblistthreshold'} || "50"; my $MailbombThreshold = $ENV{'sendmail_mailbombthreshold'} || "10"; foreach $ThisOne (sort {$MailBomber{$b}<=>$MailBomber{$a}} keys %MailBomber) { if ($MailBomber{$ThisOne} >= $MailbombThreshold and $MailBombCount < $MailbombListThreshold) { print "\n\nTop relays (recipients / connections - min $MailbombThreshold rcpts, max $MailbombListThreshold lines):" if ! $MailBombCount; printf("\n %s%5d / %4d", PrettyHost($ThisOne, 63), $MailBomber{$ThisOne}, $MailBomberConn{$ThisOne}); } $MailBombCount++; } } if (($Detail >= 5) and (keys %LoadAvg)) { eval "$PrintCond"; print "\n\nWarning!!!: "; print "Connections Rejected due to high load average $LoadAvgReject Time(s)"; my $MaxLoadAvg = 0; foreach $Load (sort keys %LoadAvg) { PrettyTimes(" Load Avg $Load", $LoadAvg{$Load}); if ($Load > $MaxLoadAvg) { $MaxLoadAvg = $Load; } } print "\n Max. Load Avg reached: $MaxLoadAvg"; } if (($Detail >= 5) and ($LoadAvgQueueSkip > 0)) { eval "$PrintCond"; print "\n"; PrettyTimes("Aborted/skipped mail queue run - load average too high", $LoadAvgQueueSkip); } if ($Detail >= 10) { foreach $StarttlsMode ('server', 'client') { if (keys %{$Starttls{$StarttlsMode}}) { eval "$PrintCond"; print "\n\nFor STARTTLS in $StarttlsMode mode,"; if (defined $Starttls{$StarttlsMode}{'OK'}) { PrettyTimes(" Requests were authenticated", $Starttls{$StarttlsMode}{'OK'}); } if (defined $Starttls{$StarttlsMode}{'TEMP'}) { PrettyTimes(" Requests had temporary errors", $Starttls{$StarttlsMode}{'TEMP'}); } if (defined $Starttls{$StarttlsMode}{'PROTOCOL'}) { PrettyTimes(" Requests had SMTP protocol errors", $Starttls{$StarttlsMode}{'PROTOCOL'}); } if (defined $Starttls{$StarttlsMode}{'SOFTWARE'}) { PrettyTimes(" Requests failed STARTTLS handshake", $Starttls{$StarttlsMode}{'SOFTWARE'}); } if (defined $Starttls{$StarttlsMode}{'NO'}) { PrettyTimes(" No cert was presented", $Starttls{$StarttlsMode}{'NO'}); } if (defined $Starttls{$StarttlsMode}{'NOT'}) { PrettyTimes(" No cert was requested", $Starttls{$StarttlsMode}{'NOT'}); } if (defined $Starttls{$StarttlsMode}{'FAIL'}) { PrettyTimes(" Cert was presented, but not verified", $Starttls{$StarttlsMode}{'FAIL'}); } if (defined $Starttls{$StarttlsMode}{'NONE'}) { PrettyTimes(" STARTTLS was not performed", $Starttls{$StarttlsMode}{'NONE'}); } if (defined $Starttls{$StarttlsMode}{'Other'}) { PrettyTimes(" Requests had unknown errors", $Starttls{$StarttlsMode}{'Other'}); } } } if (keys %StarttlsCipher) { eval "$PrintCond"; print "\n\nSTARTTLS used the following encryption mechanisms"; foreach $StarttlsCipherEntry (sort keys %StarttlsCipher) { PrettyTimes(" " . $StarttlsCipherEntry, $StarttlsCipher{$StarttlsCipherEntry}); } } if ($NoCommonName) { eval "$PrintCond"; # The following is a frequent occurrence, but not an error print "\n"; PrettyTimes("For STARTTLS, no CommonName given", $NoCommonName); } } if (($Detail >= 5) and (keys %ETRNs)) { eval "$PrintCond"; print "\n\nETRNs Received:"; foreach $ThisOne (sort keys %ETRNs) { PrettyTimes(" $ThisOne", $ETRNs{$ThisOne}); } } if(($Detail >= 10) and (keys %ReturnReceipts > 0)) { eval "$PrintCond"; print "\n\nSuccessful Return Receipts:"; foreach $ThisOne (sort keys %ReturnReceipts) { PrettyTimes(" $ThisOne", $ReturnReceipts{$ThisOne}); } } if (($Detail >= 5) and (keys %MilterHeaderCount)) { eval "$PrintCond"; print "\n\nHeaders modified by Milter:"; foreach $Header (sort keys %MilterHeaderCount) { PrettyTimes( " $Header", $MilterHeaderCount{$Header}); } } if (($Detail >= 3) and (keys %MilterDeferrals)) { eval "$PrintCond"; print "\n\nMilter transient failures:"; foreach $Reason (sort keys %MilterDeferrals) { PrettyTimes( " $Reason", $MilterDeferrals{$Reason}); } } if (($Detail >= 10) and (keys %DNSMap)) { print "\n\nDNS Map lookups:"; foreach $Domain (sort keys %DNSMap) { foreach $IP (sort keys %{$DNSMap{$Domain}}) { PrettyTimes( " $Domain => $IP", $DNSMap{$Domain}{$IP}); } } } if (($Detail >= 10) and (keys %SenderIDResults)) { print "\n\nSender-ID Results:"; foreach my $SenderIDStatus (sort keys %SenderIDResults) { PrettyTimes(" $SenderIDStatus", $SenderIDResults{$SenderIDStatus}); } } if (($Detail >= 10) and (keys %SPFResults)) { print "\n\nSPF Results:"; foreach my $SPFStatus (sort keys %SPFResults) { PrettyTimes(" $SPFStatus", $SPFResults{$SPFStatus}); } } $TotalHeaderPrinted += $HeaderPrinted; $CurrentHeader = "\n\nSMTP SESSION, MESSAGE, OR RECIPIENT ERRORS\n------------------------------------------"; $HeaderPrinted = 0; my $ErrorIndex = 0; my @TotalError = (); $TotalError[0] = 0; # SMTP Errors if (keys %TLSConnectFailed) { eval "$PrintCond" if ($Detail >= 3); print "\n\nTLS Connect Failed" if ($Detail >=3); foreach $TLSReason (sort keys %TLSConnectFailed) { PrettyTimes(" " . $TLSReason, $TLSConnectFailed{$TLSReason}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $TLSConnectFailed{$TLSReason}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if( $Detail >=3 ); } $TotalError[++$ErrorIndex] = 0; if (keys %TLSAcceptFailed) { eval "$PrintCond" if ($Detail >= 3); print "\n\nTLS Failed Access" if ($Detail >=3); foreach $TLSReason (sort keys %TLSAcceptFailed) { print "\n $TLSReason" if ($Detail >= 5); foreach $TLSFrom (sort keys %{$TLSAcceptFailed{$TLSReason}}) { PrettyTimes(" " . PrettyHost($TLSFrom, 59), $TLSAcceptFailed{$TLSReason}{$TLSFrom}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $TLSAcceptFailed{$TLSReason}{$TLSFrom}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %TLSErrno) { eval "$PrintCond" if ($Detail >= 3); print "\n\nSTARTTLS Errors" if ($Detail >= 3); foreach $Errno (sort {$a <=> $b} keys %TLSErrno) { PrettyTimes(" Error Code " . sprintf("%3d: ", $Errno) . strerror($Errno), $TLSErrno{$Errno}) if ($Detail >=5); $TotalError[$ErrorIndex] += $TLSErrno{$Errno}; } } $TotalError[++$ErrorIndex] = 0; if (keys %BadAuth) { eval "$PrintCond" if ($Detail >= 3); print "\n\nBad AUTH mechanism requests" if ($Detail >= 3); foreach $Auth (sort keys %BadAuth) { PrettyTimes(" " . $Auth, $BadAuth{$Auth}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $BadAuth{$Auth}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if( $Detail >=3 ); } $TotalError[++$ErrorIndex] = 0; if (keys %AUTHfailure) { eval "$PrintCond" if ($Detail >= 3); print "\n\nFailed AUTH requests" if ($Detail >= 3); foreach $Host (sort keys %AUTHfailure) { print "\n From " . PrettyHost($Host, 58) if ($Detail >=5); foreach $Auth (sort keys %{$AUTHfailure{$Host}}) { PrettyTimes(" $Auth", $AUTHfailure{$Host}{$Auth}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $AUTHfailure{$Host}{$Auth}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %AUTHnouser) { eval "$PrintCond" if ($Detail >= 3); print "\n\nFailed AUTH requests because of No User" if ($Detail >= 3); my $UserCount = CountOrder(%AUTHnouser); foreach $User (sort $UserCount keys %AUTHnouser) { PrettyTimes(" $User", $AUTHnouser{$User}) if ($Detail >=5); $TotalError[$ErrorIndex] += $AUTHnouser{$User}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if($RemoteProtocolError > 0) { eval "$PrintCond" if ($Detail >= 3); print "\n\n" . $RemoteProtocolError . " Remote Protocol Errors" if ($Detail >= 3); $TotalError[$ErrorIndex] += $RemoteProtocolError } $TotalError[++$ErrorIndex] = 0; if (keys %AttackAttempt) { eval "$PrintCond"; print "\n\nWARNING!!!! Possible Attack:"; foreach $Host (sort {$a cmp $b} keys %AttackAttempt) { print "\n Attempt from $Host with:"; foreach $Attack (sort {$a cmp $b} keys %{$AttackAttempt{$Host}}) { PrettyTimes(" $Attack", $AttackAttempt{$Host}{$Attack}); $TotalError[$ErrorIndex] += $AttackAttempt{$Host}{$Attack}; } } print "\n\tTotal: $TotalError[$ErrorIndex]"; } $TotalError[++$ErrorIndex] = 0; if (keys %KnownSpammer) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $KnownSpammerThreshold = $ENV{'sendmail_knownspammerthreshold'} || "1"; my $KnownSpammerCount = CountOrder(%KnownSpammer); print "\n\nMail attempts from known spammers: [Occurrences >= $KnownSpammerThreshold]" if ($Detail >= 3); foreach $ThisOne (sort $KnownSpammerCount keys %KnownSpammer) { if ($KnownSpammer{$ThisOne} >= $KnownSpammerThreshold) { PrettyTimes(" " . PrettyHost($ThisOne, 63), $KnownSpammer{$ThisOne}) if ($Detail >= 5); } $TotalError[$ErrorIndex] += $KnownSpammer{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %RelayDenied) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $RelayDeniedThreshold = $ENV{'sendmail_relaydeniedthreshold'} || "1"; print "\n\nRelaying denied: [Occurrences >= $RelayDeniedThreshold]" if ($Detail >= 3); my $RelayCount = TotalCountOrder(%RelayDenied); foreach $Relay (sort $RelayCount keys %RelayDenied) { $RelayDeniedCount = 0; my $DestCount = CountOrder(%{$RelayDenied{$Relay}}); foreach $Dest (sort $DestCount keys %{$RelayDenied{$Relay}}) { $RelayDeniedCount += $RelayDenied{$Relay}{$Dest}; $TotalError[$ErrorIndex] += $RelayDenied{$Relay}{$Dest}; } if ($RelayDeniedCount >= $RelayDeniedThreshold) { printf("\n From %s", PrettyHost($Relay, 58)) if ($Detail >=5); foreach $Dest (keys %{$RelayDenied{$Relay}}) { PrettyTimes(" to " . $Dest, $RelayDenied{$Relay}{$Dest}) if ($Detail >= 5); } } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %CheckMailReject) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $CheckMailRejectThreshold = $ENV{'sendmail_checkmailrejectthreshold'} || "1"; print "\n\nRejected incoming mail: [Occurrences >= $CheckMailRejectThreshold]" if ($Detail >= 3); foreach $ThisOne (keys %CheckMailReject) { if ($CheckMailReject{$ThisOne} >= $CheckMailRejectThreshold) { PrettyTimes(" $ThisOne", $CheckMailReject{$ThisOne}) if ($Detail >= 5); } $TotalError[$ErrorIndex] += $CheckMailReject{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %PREGreeting) { eval "$PrintCond" if ($Detail >= 3); #Set up default -mgt my $PREGreetingThreshold = $ENV{'sendmail_pregreetingthreshold'} || "1"; my $PREGreetingCount = CountOrder(%PREGreeting); print "\n\nGreet Pause Rejections: [Occurrences >= $PREGreetingThreshold]" if ($Detail >= 3); foreach my $ip (sort $PREGreetingCount keys %PREGreeting) { PrettyTimes(" " . PrettyHost($ip, 63), $PREGreeting{$ip}) if ($Detail >= 5) && ($PREGreeting{$ip} >= $PREGreetingThreshold); $TotalError[$ErrorIndex] += $PREGreeting{$ip}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %LostInputChannel) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $LostInputChannelThreshold = $ENV{'sendmail_lostinputchannelthreshold'} || "1"; my $LostInputChannelCount = CountOrder(%LostInputChannel); print "\n\nLost input channel: [Occurrences >= $LostInputChannelThreshold]" if ($Detail >= 3); foreach $ThisOne (sort $LostInputChannelCount keys %LostInputChannel) { if ($LostInputChannel{$ThisOne} >= $LostInputChannelThreshold) { PrettyTimes(" " . PrettyHost($ThisOne, 63), $LostInputChannel{$ThisOne}) if ($Detail >= 5); } $TotalError[$ErrorIndex] += $LostInputChannel{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %DummyConnection) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $DummyConnectionThreshold = $ENV{'sendmail_dummyconnectionthreshold'} || "1"; my $DummyConnectionCount = CountOrder(%DummyConnection); print "\n\nClient quit before communicating: [Occurrences >= $DummyConnectionThreshold]" if ($Detail >= 3); foreach $ThisOne (sort $DummyConnectionCount keys %DummyConnection) { if ($DummyConnection{$ThisOne} >= $DummyConnectionThreshold) { PrettyTimes(" " . PrettyHost($ThisOne, 63), $DummyConnection{$ThisOne}) if ($Detail >= 5); } $TotalError[$ErrorIndex] += $DummyConnection{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %DomainErrors) { eval "$PrintCond" if ($Detail >= 3); #Set up default -mgt my $DomainErrorsThreshold = $ENV{'sendmail_domainerrorsthreshold'} || "1"; print "\n\nUnresolveable or non-existent domains: [Occurrences >= $DomainErrorsThreshold]" if ($Detail >= 3); my $count = TotalCountOrder(%DomainErrors); foreach $ThisOne (sort $count keys %DomainErrors) { my $subcount=CountOrder(%{$DomainErrors{$ThisOne}}); my $sublist; my $subcounter = 0; foreach $User (sort $subcount keys %{$DomainErrors{$ThisOne}}) { $subcounter += $DomainErrors{$ThisOne}{$User}; } if ( $subcounter >= $DomainErrorsThreshold) { PrettyTimes(" From " . PrettyHost($ThisOne, 58), $subcounter) if ($Detail >=5); foreach $User (sort $subcount keys %{$DomainErrors{$ThisOne}}) { PrettyTimes(" $User", $DomainErrors{$ThisOne}{$User}) if ($Detail >= 10); } } $TotalError[$ErrorIndex] += $subcounter; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %AuthWarns) { eval "$PrintCond" if ($Detail >= 3); print "\n\nAuthentication warnings:" if ($Detail >= 3); foreach $ThisOne (sort keys %AuthWarns) { PrettyTimes(" $ThisOne", $AuthWarns{$ThisOne}) if ($Detail >= 5);; $TotalError[$ErrorIndex] += $AuthWarns{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %Timeouts) { eval "$PrintCond" if ($Detail >= 3); #Set up default -mgt my $TimeoutThreshold = $ENV{'sendmail_timeoutthreshold'} || "1"; print "\n\nTimeouts: [Occurrences >= $TimeoutThreshold]" if ($Detail >= 3); my $TimeoutCount = CountOrder(%Timeouts); foreach $ThisOne (sort $TimeoutCount keys %Timeouts) { PrettyTimes(" $ThisOne", $Timeouts{$ThisOne}) if (($Detail >= 5) && ($Timeouts{$ThisOne} >= $TimeoutThreshold)); $TotalError[$ErrorIndex] += $Timeouts{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %Abuse) { eval "$PrintCond" if ($Detail >= 3); my $TotalAbuse; print "\n\nRejected VRFY/EXPN/ETRN (host,ruser):" if ($Detail >= 3); foreach $Host (sort keys %Abuse) { print "\n $Host" if ($Detail >= 5); $TotalAbuse = 0; foreach $Luser (sort keys %{$Abuse{$Host}}) { print "\n $Luser:" if ($Detail >= 5); foreach $RejCmd (sort keys %{$Abuse{$Host}{$Luser}}) { PrettyTimes(" $RejCmd", $Abuse{$Host}{$Luser}{$RejCmd}) if ($Detail >= 5); $TotalAbuse += $Abuse{$Host}{$Luser}{$RejCmd}; } } print "\n Total per host: $TotalAbuse" if ($Detail >= 5); $TotalError[$ErrorIndex] += $TotalAbuse; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %CommandUnrecognized) { eval "$PrintCond" if ($Detail >= 3); print "\n\nSet(s) of unrecognized SMTP commands:" if ($Detail >= 3); foreach $ThisOne (keys %CommandUnrecognized) { print "\n From QueueID: $ThisOne" if ($Detail >= 5); print "\n$CommandUnrecognized{$ThisOne}" if ($Detail >= 5); $TotalError[$ErrorIndex] ++; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %StarttlsCert) { eval "$PrintCond" if ($Detail >= 3); print "\n\nSTARTTLS failed to verify certificates:" if ($Detail >= 3); foreach $ThisOne (sort keys %StarttlsCert) { printf "\n %s:", $ThisOne if ($Detail >= 5); foreach $CommonName (sort keys %{$StarttlsCert{$ThisOne}}) { PrettyTimes(" CN: $CommonName", $StarttlsCert{$ThisOne}{$CommonName}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $StarttlsCert{$ThisOne}{$CommonName}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; # Message errors if ($OverSize > 0) { eval "$PrintCond" if ($Detail >= 3); print "\n\nRejected $OverSizeBytes bytes in $OverSize message(s)" if ($Detail >= 3); $TotalError[$ErrorIndex] += $OverSize; } $TotalError[++$ErrorIndex] = 0; if (keys %CollectError) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default per Reason my $CollectErrorThreshold = $ENV{'sendmail_collecterrorthreshold'} || "1"; print "\n\nErrors during Collect:" if ($Detail >= 3); foreach $Reason (sort keys %CollectError) { print "\n $Reason: [Occurrences >= $CollectErrorThreshold]" if ($Detail >= 5); my $colerror = 0; foreach $Source (keys %{$CollectError{$Reason}}) { if ($CollectError{$Reason}{$Source} >= $CollectErrorThreshold) { PrettyTimes(" $Source", $CollectError{$Reason}{$Source}) if ($Detail >= 5); } $colerror = $colerror + $CollectError{$Reason}{$Source}; } $TotalError[$ErrorIndex] += $colerror; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %BadRcptThrottle) { eval "$PrintCond" if ($Detail >= 3); my $BadRcptThrottleThreshold = $ENV{'sendmail_badrcptthrottlethreshold'} || "1"; my $BadRcptCount = CountOrder(%BadRcptThrottle); print "\n\nClient submitted too many bad recipients: [Occurrences >= $BadRcptThrottleThreshold]" if ($Detail >= 3); foreach $ThisOne (sort $BadRcptCount keys %BadRcptThrottle) { PrettyTimes(" " . PrettyHost($ThisOne,61), $BadRcptThrottle{$ThisOne}) if ($Detail >= 5) && ( $BadRcptThrottle{$ThisOne} >= $BadRcptThrottleThreshold ); $TotalError[$ErrorIndex] += $BadRcptThrottle{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if ($TooManyRcpts > 0) { eval "$PrintCond" if ($Detail >= 3); print "\n\n$TooManyRcpts messages with too many recipients" if ($Detail >= 3); $TotalError[$ErrorIndex] += $TooManyRcpts; } $TotalError[++$ErrorIndex] = 0; if (keys %LargeHdrs) { eval "$PrintCond" if ($Detail >= 3); print "\n\nToo large headers from:" if ($Detail >= 3); foreach $Host ( sort {$LargeHdrs{$b}<=>$LargeHdrs{$a}} keys %LargeHdrs ) { PrettyTimes(" $Host", $LargeHdrs{$Host}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $LargeHdrs{$Host}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %AddressError) { eval "$PrintCond" if ($Detail >= 3); print "\n\nErrors in mail address:" if ($Detail >= 3); foreach $Reason (sort {$a cmp $b} keys %AddressError) { print "\n $Reason:" if ($Detail >= 5); foreach $Address (sort {$a cmp $b} keys %{$AddressError{$Reason}}) { PrettyTimes(" $Address", $AddressError{$Reason}{$Address}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $AddressError{$Reason}{$Address}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %Quarantined) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $QuarantinedThreshold = $ENV{'sendmail_quarantinedthreshold'} || "1"; my $QuarantinedCount = CountOrder(%Quarantined); print "\n\nMessages quarantined by milter: [Occurrences >= $QuarantinedThreshold]" if ($Detail >= 3); foreach $ThisOne (sort $QuarantinedCount keys %Quarantined) { if ($Quarantined{$ThisOne} >= $QuarantinedThreshold) { PrettyTimes(" $ThisOne", $Quarantined{$ThisOne}) if ($Detail >= 5); } $TotalError[$ErrorIndex] += $Quarantined{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; # Recipient errors if (keys %SentTimeouts) { eval "$PrintCond" if ($Detail >= 3); print "\n\nMessages delayed by recipients:" if ($Detail >= 3); foreach $ThisOne (keys %SentTimeouts) { PrettyTimes(" $ThisOne", $SentTimeouts{$ThisOne}) if ($Detail >=5); $TotalError[$ErrorIndex] += $SentTimeouts{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %UnknownUsers) { eval "$PrintCond" if ($Detail >= 3); %SortedUsers = (); foreach $Usr (sort keys %UnknownUsers) { foreach $QueueID (sort keys %{ $UnknownUsers{$Usr} }) { $SortedUsers{$Usr}{$Msgs{$QueueID}{"Relay"}}++; $TotalError[$ErrorIndex] ++; } } my $UnknownUsersThreshold = $ENV{'sendmail_unknownusersthreshold'} || "1"; if ($UnknownUsersThreshold) { print "\n\nUnknown local users: [Occurrences >= $UnknownUsersThreshold]" if ($Detail >= 3); } else { print "\n\nUnknown local users:" if ($Detail >= 3); } if ($Detail >= 5) { my $count = TotalCountOrder( %SortedUsers ); foreach $Usr (sort $count keys %SortedUsers) { my $subcount = CountOrder( %{$SortedUsers{$Usr}} ); my $UnknownUsersCount = 0; foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) { $UnknownUsersCount += $SortedUsers{$Usr}{$RelayHost}; } if ($UnknownUsersCount >= $UnknownUsersThreshold) { PrettyTimes(" " . $Usr, $UnknownUsersCount); if ($Detail >= 15) { foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) { PrettyTimes(" from " . PrettyHost($RelayHost, 54), $SortedUsers{$Usr}{$RelayHost}); } } } } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %UnknownUsersCheckRcpt) { eval "$PrintCond" if ($Detail >= 3); %SortedUsers = (); foreach $Usr (sort keys %UnknownUsersCheckRcpt) { foreach $QueueID (sort keys %{ $UnknownUsersCheckRcpt{$Usr} }) { $SortedUsers{$Usr}{$Msgs{$QueueID}{"Relay"}}++; $TotalError[$ErrorIndex] ++; } } my $UnknownUsersThreshold = $ENV{'sendmail_unknownusersthreshold'} || "1"; if ($UnknownUsersThreshold) { print "\n\nUnknown users: [Occurrences >= $UnknownUsersThreshold]" if ($Detail >= 3); } else { print "\n\nUnknown users:" if ($Detail >= 3); } if ($Detail >= 5) { my $count = TotalCountOrder( %SortedUsers ); foreach $Usr (sort $count keys %SortedUsers) { my $subcount = CountOrder( %{$SortedUsers{$Usr}} ); my $UnknownUsersCount = 0; foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) { $UnknownUsersCount += $SortedUsers{$Usr}{$RelayHost}; } if ($UnknownUsersCount >= $UnknownUsersThreshold) { PrettyTimes(" " . $Usr, $UnknownUsersCount); if ($Detail >= 15) { foreach $RelayHost (sort $subcount keys %{ $SortedUsers{$Usr} }) { PrettyTimes(" from " . PrettyHost($RelayHost, 54), $SortedUsers{$Usr}{$RelayHost}); } } } } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %DisabledMailbox) { eval "$PrintCond" if ($Detail >= 3); %SortedUsers = (); foreach $Usr (sort keys %DisabledMailbox) { foreach $QueueID (sort keys %{ $DisabledMailbox{$Usr} }) { $SortedUsers{$Usr}{$Msgs{$QueueID}{"Relay"}}++; $TotalError[$ErrorIndex] ++; } } print "\n\nDisabled mailboxes:" if ($Detail >= 3); foreach $Usr (sort keys %SortedUsers) { print "\n $Usr" if ($Detail >= 5); foreach $RelayHost (sort keys %{ $SortedUsers{$Usr} }) { PrettyTimes(" from " . PrettyHost($RelayHost, 54), $SortedUsers{$Usr}{$RelayHost}) if ($Detail >= 5); } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %CheckRcptReject) { eval "$PrintCond" if ($Detail >= 3); #Set Threshold default my $CheckRcptRejectThreshold = $ENV{'sendmail_checkrcptrejectthreshold'} || "1"; print "\n\nRejected mail: [Occurrences >= $CheckRcptRejectThreshold]" if ($Detail >= 3); foreach $ThisOne (keys %CheckRcptReject) { if ($CheckRcptReject{$ThisOne} >= $CheckRcptRejectThreshold) { PrettyTimes(" $ThisOne", $CheckRcptReject{$ThisOne}) if ($Detail >= 5); } $TotalError[$ErrorIndex] += $CheckRcptReject{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %StatRejected) { eval "$PrintCond" if ($Detail >= 3); print "\n\nMail Rejected:" if ($Detail >= 3); foreach $Reason (sort keys %StatRejected) { print "\n $Reason:" if ($Detail >= 5); foreach $ToUser (keys %{$StatRejected{$Reason}}) { PrettyTimes(" " . $ToUser, $StatRejected{$Reason}{$ToUser}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $StatRejected{$Reason}{$ToUser}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %StatDeferred) { eval "$PrintCond" if ($Detail >= 3); print "\n\nMail Deferred:" if ($Detail >= 3); foreach $Reason (sort keys %StatDeferred) { print "\n $Reason" if ($Detail >= 5); foreach $ToUser (keys %{$StatDeferred{$Reason}}) { PrettyTimes(" To: $ToUser", $StatDeferred{$Reason}{$ToUser}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $StatDeferred{$Reason}{$ToUser}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %ForwardErrors) { eval "$PrintCond" if ($Detail >= 3); print "\n\nForwarding errors:" if ($Detail >= 3); my $FECount = CountOrder(%ForwardErrors); foreach $ThisOne (sort $FECount keys %ForwardErrors) { PrettyTimes(" $ThisOne", $ForwardErrors{$ThisOne}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $ForwardErrors{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; # Other errors not originating in base (stock) sendmail distribution if (keys %BlackHoled) { eval "$PrintCond" if ($Detail >= 3); print "\n\nBlackHole Totals:" if ($Detail >= 3); foreach $ThisOne (sort keys %BlackHoles) { PrettyTimes(" $ThisOne", $BlackHoles{$ThisOne}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $BlackHoles{$ThisOne}; } if ($Detail >= 10) { print "\n\nBlackholed:"; my $BlackHoleThreshold = $ENV{'sendmail_blackholethreshold'} || "1"; foreach $ThisOne (sort keys %BlackHoled) { if ($BlackHoled{$ThisOne} >= $BlackHoleThreshold) { PrettyTimes(" $ThisOne", $BlackHoled{$ThisOne}); } } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if($XS4ALL > 0) { eval "$PrintCond" if ($Detail >= 3); print "\n\n$XS4ALL messages discarded from XS4ALL" if ($Detail >= 3); $TotalError[$ErrorIndex] += $XS4ALL; } $TotalError[++$ErrorIndex] = 0; if ($CantCreateOutput > 0) { eval "$PrintCond" if ($Detail >= 3); print "\n\nCan't create output $CantCreateOutput Time(s)" if ($Detail >= 3); $TotalError[$ErrorIndex] += $CantCreateOutput; } $TotalError[++$ErrorIndex] = 0; if (keys %MailRejected) { eval "$PrintCond" if ($Detail >= 3); print "\n\nMail was rejected because of the following entries in the access database:" if ($Detail >= 3); foreach $ThisOne (sort keys %MailRejected) { PrettyTimes(" $ThisOne", $MailRejected{$ThisOne}) if ($Detail >= 5); $TotalError[$ErrorIndex] += $MailRejected{$ThisOne}; } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3); } $TotalError[++$ErrorIndex] = 0; if (keys %RelayReject) { eval "$PrintCond" if ($Detail >= 3); print "\n\nWe do not relay for these (host,ruser,luser):" if ($Detail >= 3);; foreach $Host (sort keys %RelayReject) { print "\n $Host" if ($Detail >= 5); foreach $Ruser (sort keys %{ $RelayReject{$Host} }) { print "\n $Ruser" if ($Detail >= 5); foreach $Luser (sort keys %{$RelayReject{$Host}{$Ruser}}) { printf "\n %-30s %i", $Luser,$RelayReject{$Host}{$Ruser}{$Luser} if ($Detail >= 5); $TotalError[$ErrorIndex] += $RelayReject{$Host}{$Ruser}{$Luser}; } } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3);; } $TotalError[++$ErrorIndex] = 0; if (keys %NotLocal) { eval "$PrintCond" if ($Detail >= 3); print "\n\nAddress not local from these (host, user):" if ($Detail >= 3);; foreach $Host (sort keys %NotLocal ) { print "\n $Host" if ($Detail >= 5); foreach $Luser (sort keys %{ $NotLocal{$Host} }) { printf "\n %-30s %i",$Luser,$NotLocal{$Host}{$Luser} if ($Detail >= 5); $TotalError[$ErrorIndex] += $NotLocal{$Host}{$Luser}; } } print "\n\tTotal: $TotalError[$ErrorIndex]" if ($Detail >= 3);; } $TotalError[++$ErrorIndex] = 0; if (keys %RuleSets) { print "\n\nRuleset violations:"; foreach $Reason (sort keys %RuleSets) { PrettyTimes(" $Reason", $RuleSets{$Reason}); $TotalError[$ErrorIndex] += $RuleSets{$Reason}; } } $TotalError[++$ErrorIndex] = 0; eval $ReportFilter; if ($@) { print $@; print "While processing ReportFilter:\n$ReportFilter\n"; } $TotalCount = 0; foreach $ErrorCount (@TotalError) { if (defined $ErrorCount) { $TotalCount += $ErrorCount; } } if ( ($TotalCount > 0) && ($Detail >= 3)) { eval "$PrintCond"; print "\n\nSummary of SMTP Session, Message, and Recipient Errors handled by Sendmail:"; print "\n\tTotal: $TotalCount"; } if (keys %MilterErrors) { eval "$PrintCond"; print "\n\nMilter Errors:\n"; foreach my $Milter (sort {$a cmp $b} keys %MilterErrors) { print " $Milter:\n"; foreach $Error (sort {$a cmp $b} keys %{$MilterErrors{$Milter}}) { PrettyTimes(" $Error", $MilterErrors{$Milter}{$Error}); } } } if (keys %OtherList) { $HeaderPrinted = 1; print "\n\n**Unmatched Entries**"; foreach my $line (sort {$OtherList{$b}<=>$OtherList{$a} } keys %OtherList) { PrettyTimes(" $line", $OtherList{$line}); } } if ($TotalHeaderPrinted > 0) { print "\n"; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/postgresql0000644000175000017500000001316715075123620021130 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # # Logwatch service for postgresql error log # # Processes all messages and summarizes them # Each message is given with a timestamp and RMS # ######################################################## # (C) 2011 by Dalibo - http://www.dalibo.com/ # written by Gilles Darold. # # Heavily based on the mysql script by Jeremias Reith ######################################################## ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution and the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################## use strict; use Logwatch ':dates'; use Time::Local; use POSIX qw(strftime); # Allow timestamp from two different logfile format: syslog and stderr my $date_format1 = '%Y-%m-%d %H:%M:%S'; my $date_format2 = '%b %e %H:%M:%S'; my $filter1 = TimeFilter($date_format1); my $filter2 = TimeFilter($date_format2); # Allow summarization of WARNING and HINT too if wanted my $detail = exists $ENV{'LOGWATCH_DETAIL_LEVEL'} ? $ENV{'LOGWATCH_DETAIL_LEVEL'} : 0; # Used to replace the month trigram into the syslog timestamp my %month2num = ( Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5, Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11 ); # Array of the relevant lines in the log file. # First element: type of event # Second element: matching regexp ($1 should contain the message) # Third element: anonymous hash ref (stores message counts) my @message_categories = ( ['Panics', qr/PANIC: (.*)$/o, {}], ['Fatals', qr/FATAL: (.*)$/o, {}], ['Errors', qr/ERROR: (.*)$/o, {}] ); if ($detail) { # Add more log information push(@message_categories, ['Warnings', qr/WARNING: (.*)$/o, {}], ['Hints', qr/HINT: (.*)$/o, {}] ); } # Set the current year as syslog don't have this information. my $cur_year = (localtime(time))[5]; # Parse messages from stdin while(<>) { my $line = $_; # skipping messages that are not within the requested range next unless $line =~ /^($filter1|$filter2)/o; my $datetime = $1; my $time = ''; # Date/time format differ following the log_destination (stderr or syslog) if ($datetime =~ /(\d{4})-(\d{2})-(\d{2})\s+(\d+):(\d+):(\d+)/) { { # timelocal is quite chatty local $SIG{'__WARN__'} = sub {}; $time = timelocal($6, $5, $4, $3, $2-1, $1-1900); } } elsif ($datetime =~ /(\w)\s+(\d+)\s+(\d+):(\d+):(\d+)/) { { # timelocal is quite chatty local $SIG{'__WARN__'} = sub {}; $time = timelocal($5, $4, $3, $2, $month2num{$1}, $cur_year); } } # Remove character position $line =~ s/ at character \d+//; foreach my $cur_cat (@message_categories) { if($line =~ /$cur_cat->[1]/) { my $msgs = $cur_cat->[2]; $msgs->{$1} = {count => '0', first_occurrence => $time, sum => 0, sqrsum => 0} unless exists $msgs->{$1}; $msgs->{$1}->{'count'}++; # summing up timestamps and squares of timestamps # in order to calculate the rms # using first occurrence of message as offset in calculation to # prevent an integer overflow $msgs->{$1}->{'sum'} += $time - $msgs->{$1}->{'first_occurrence'}; $msgs->{$1}->{'sqrsum'} += ($time - $msgs->{$1}->{'first_occurrence'}) ** 2; last; } } } # generating summary foreach my $cur_cat (@message_categories) { # skipping non-requested message types next unless keys %{$cur_cat->[2]}; my ($name, undef, $msgs) = @{$cur_cat}; print $name, ":\n"; print '-' x (length($name)+1), "\n"; my $last_count = 0; # sorting messages by count my @sorted_msgs = sort { $msgs->{$b}->{'count'} <=> $msgs->{$a}->{'count'} } keys %{$msgs}; foreach my $msg (@sorted_msgs) { # grouping messages by number of occurrence print "\n", $msgs->{$msg}->{'count'}, " times:\n" unless $last_count == $msgs->{$msg}->{'count'}; my $rms = 0; # printing timestamp print '['; if($msgs->{$msg}->{'count'} > 1) { # calculating rms $rms = int(sqrt( ($msgs->{$msg}->{'count'} * $msgs->{$msg}->{'sqrsum'} - $msgs->{$msg}->{'sum'}) / ($msgs->{$msg}->{'count'} * ($msgs->{$msg}->{'count'} - 1)))); print strftime($date_format1, localtime($msgs->{$msg}->{'first_occurrence'}+int($rms/2))); print ' +/-'; # printing rms if($rms > 86400) { print int($rms/86400) , ' day(s)'; } elsif($rms > 3600) { print int($rms/3600) , ' hour(s)'; } elsif($rms > 60) { print int($rms/60) , ' minute(s)'; } else { print $rms, ' seconds'; } } else { # we have got this message a single time print strftime($date_format1, localtime($msgs->{$msg}->{'first_occurrence'})); } print '] ', $msg, "\n"; $last_count = $msgs->{$msg}->{'count'}; } print "\n"; } logwatch-7.14/scripts/services/dnf50000644000175000017500000001723315122642254017561 0ustar logwatchlogwatch######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2025 Frank Crawford ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### ######################################################### ## Based on a script by Matthew McGillis ######################################################### use lib "/usr/share/logwatch/lib"; use strict; use POSIX; use Logwatch ':dates'; eval "use Date::Manip"; my $hasDM = $@ ? 0 : 1; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $DebugCounter = 0; my $SearchDate = TimeFilter($ARGV[0] || '%Y-%m-%d %H:%M:%S'); my $pathto_dnf5 = $ENV{'pathto_dnf5'} || '/usr/bin/dnf'; my $dnf5_uses_UTC = $ENV{'dnf5_uses_utc'} || 0; if ( $Debug >= 5 ) { print STDERR "\nDEBUG: Inside dnf5 Filter \n"; print STDERR "DEBUG: Path to dnf: $pathto_dnf5\n"; print STDERR "DEBUG: Looking For: " . $SearchDate . "\n"; print STDERR "DEBUG: dnf5_uses_UTC: " . $dnf5_uses_UTC . "\n"; $DebugCounter = 1; } open(VERSION, '-|', $pathto_dnf5, '--version') || exit(0); my $version = ; if ( $version !~ "dnf5" ) { if ( $Debug >= 5 ) { print STDERR "DEBUG: Version does not match dnf5: $version\n"; } exit(1); } if ( !$hasDM and $dnf5_uses_UTC and $Debug ) { print STDERR "DEBUG: Date::Manip not found so cannot convert from UTC to localtime\n"; } #Init Hashes my ( %PackageInstall, %PackageUpgrade, %PackageDowngrade, %PackageReinstall, %PackageRemove, %PackageReplaced ); my @InfoMessages = (); open(LISTLINE, '-|', $pathto_dnf5, 'history', 'list', '--reverse') || die "can't run $pathto_dnf5: $!"; while ( my $ListLine = ) { my ($ID, $Cmd, $Date, $Count) = ($ListLine =~ /([ \d]+) +(.*) +([\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}) +(\d+)/); if ( $Debug >= 20 ) { print STDERR "DEBUG($DebugCounter): $ListLine"; $DebugCounter++; } if ( $dnf5_uses_UTC and defined($Date) ) { my $new_date = ParseDate($Date); $new_date = Date_ConvTZ($new_date, "UTC") if $new_date; if ( ! $new_date ) { print STDERR "DEBUG($DebugCounter): Error converting date from UTC to localtime: $Date\n" if $Debug; } else { $Date = UnixDate( $new_date, $ARGV[0] || '%Y-%m-%d %H:%M:%S' ); print STDERR "DEBUG($DebugCounter): Local Date: $Date\n" if $Debug >= 20; } } if ( $Date =~ /^$SearchDate/o ) { open (INFOLINE, '-|', $pathto_dnf5, 'history', 'info', "$ID") || die "can't run $pathto_dnf5: $!"; while ( my $InfoLine = ) { if ( $Debug >= 10 and $InfoLine =~ /^[^\s]/ ) { print STDERR "DEBUG($DebugCounter): $InfoLine"; $DebugCounter++; } if ( $InfoLine =~ /(Groups|Environments) altered:/ ) { push(@InfoMessages, $InfoLine); } next if ( $InfoLine =~ /^\s*$/ or $InfoLine =~ /^[^\s]/ ); my ($Action, $Package, $Reason, $Repository) = ($InfoLine =~ / *([^\s]+) +([^\s]+) +(.+) +([^\s]+)$/); if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $InfoLine"; print STDERR "DEBUG($DebugCounter): Action = $Action, Package = $Package\n"; $DebugCounter++; } if ( $Action !~ "Action" ) { my ($Name, $EVRA, $Arch) = ($Package =~ /([^\s]+)-(\d+:[^\s-]+-[^\s]+\.([^\s.]+))/); if ( $Debug >= 5 ) { my $DC = $DebugCounter - 1; print STDERR "DEBUG($DC): Name = $Name, EVRA = $EVRA, Arch = $Arch\n"; } if ( !defined($Name) ) { push(@InfoMessages, $InfoLine); } elsif ( $Action =~ "Install" ) { $PackageInstall{$Name . ":" . $Arch} = $EVRA; } elsif ( $Action =~ "Upgrade" ) { $PackageUpgrade{$Name . ":" . $Arch} = $EVRA; } elsif ( $Action =~ "Downgrade" ) { $PackageDowngrade{$Name . ":" . $Arch} = $EVRA; } elsif ( $Action =~ "Reinstall" ) { $PackageReinstall{$Name . ":" . $Arch} = $EVRA; } elsif ( $Action =~ "Remove" ) { $PackageRemove{$Name . ":" . $Arch} = $EVRA; } elsif ( $Action =~ "Replaced" ) { $PackageReplaced{$Name . ":" . $Arch} = $EVRA; } else { push(@InfoMessages, $InfoLine); } } } } } if (keys %PackageInstall) { print "\nPackages Installed:\n"; foreach my $ThisOne (sort {lc($a) cmp lc($b)} keys %PackageInstall) { my ($Name, $Arch) = ($ThisOne =~ /([^\s]+):([^\s]+)/ ); print " " . $Name . ": " . $PackageInstall{$ThisOne} . "\n"; } } if (keys %PackageReinstall) { print "\nPackages Reinstalled:\n"; foreach my $ThisOne (sort {lc($a) cmp lc($b)} keys %PackageReinstall) { my ($Name, $Arch) = ($ThisOne =~ /([^\s]+):([^\s]+)/ ); print " " . $Name . ": " . $PackageReinstall{$ThisOne} . "\n"; } } if (keys %PackageUpgrade) { print "\nPackages Updated:\n"; foreach my $ThisOne (sort {lc($a) cmp lc($b)} keys %PackageReplaced) { if ( defined($PackageUpgrade{$ThisOne}) ) { my ($Name, $Arch) = ($ThisOne =~ /([^\s]+):([^\s]+)/ ); print " " . $Name . ": " . $PackageReplaced{$ThisOne} . " -> " . $PackageUpgrade{$ThisOne} . "\n"; } elsif ( !defined($PackageDowngrade{$ThisOne}) ) { if ( $Debug >= 5 ) { print STDERR "\nDEBUG($DebugCounter): Action = Updated->Removed, Package = $ThisOne\n"; $DebugCounter++; } $PackageRemove{$ThisOne} = $PackageReplaced{$ThisOne}; } } } if (keys %PackageDowngrade) { print "\nPackages Downgraded:\n"; foreach my $ThisOne (sort {lc($a) cmp lc($b)} keys %PackageReplaced) { if ( defined($PackageDowngrade{$ThisOne}) ) { my ($Name, $Arch) = ($ThisOne =~ /([^\s]+):([^\s]+)/ ); print " " . $Name . ": " . $PackageReplaced{$ThisOne} . " -> " . $PackageDowngrade{$ThisOne} . "\n"; } elsif ( !defined($PackageUpgrade{$ThisOne}) ) { if ( $Debug >= 5 ) { print STDERR "\nDEBUG($DebugCounter): Action = Downgraded->Removed, Package = $ThisOne\n"; $DebugCounter++; } $PackageRemove{$ThisOne} = $PackageReplaced{$ThisOne}; } } } if (keys %PackageRemove) { print "\nPackages Erased:\n"; foreach my $ThisOne (sort {lc($a) cmp lc($b)} keys %PackageRemove) { my ($Name, $Arch) = ($ThisOne =~ /([^\s]+):([^\s]+)/ ); print " " . $Name . ": " . $PackageRemove{$ThisOne} . "\n"; } } if (@InfoMessages) { print "\nInformation Messages:\n"; foreach my $ThisOne (@InfoMessages) { print " " . $ThisOne; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 logwatch-7.14/scripts/services/evtapplication0000644000175000017500000002154215075123620021743 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2008 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use warnings; use URI::URL; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Ignore_messages = $ENV{'ignore_messages'} || '^$'; my $Ignore_profile_program = $ENV{'ignore_profile_program'} || '^$'; my $Laptops = $ENV{'laptops'} || '^$'; my %Applications; while (defined(my $ThisLine = )) { # User specified ignore messages, lower cased next if $ThisLine =~ /$Ignore_messages/i; my ($Criticality,$SourceName,$DateTime,$EventID,$Application,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra); #Determine format if ($ThisLine =~ /MSWinEventLog\[/) { # Snare 4 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$Application,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /(\S+)\sMSWinEventLog\[(\d+)\]:(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } elsif ($ThisLine =~ /MSWinEventLog\t/) { # Snare 3 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$Application,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\t(\d+)\t(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } if (!defined($Hostname)) { print STDERR "Cannot parse $ThisLine"; next; } next if $EventLogType eq "Information" and $ExpandedString !~ "BlueScreen"; next if $ExpandedString eq "N/A"; # Modify some items that prevent de-duplication if ($Detail < 10) { $ExpandedString =~ s/(NextScheduled\S+|PID) \d+/$1 XXX/; $ExpandedString =~ s,\d{4}/\d\d/\d\d \d\d:\d\d:\d\d(?:\.\d+)?,TIME,g; $ExpandedString =~ s/(?:\w{3}, )?\d{2} \w{3} \d{4},? \d\d:\d\d(?::\d\d \w{3})?/TIME/g; $ExpandedString =~ s/(SessionId|ThreadId):( ?0x)[0-9A-Fa-f]{2,16}(?::0x[0-9a-f]{5})?/$1:${2}XXXX/g; $ExpandedString =~ s/Session-trace:.*$/Session-trace: XXXX/; } #print STDERR "ExpandedString = $ExpandedString\n"; if ($Application =~ /Userenv/) { $ExpandedString = "$UserName $ExpandedString"; } if ($Application eq "Application Error") { if (my ($exe, $exever, $module, $modulever) = ($ExpandedString =~ /Faulting application name: (.*), version: (\S+), time stamp: .*Faulting module name: (.*), version: (\S+)/)) { $Applications{$Application}->{"$Hostname: Faulting application name: $exe, version: $exever, module name: $module, version $modulever"}++; next; } } elsif ($Application eq "Application Hang") { if (my ($exe, $exever, $msg) = ($ExpandedString =~ /The program (.*) version (\S+) (.*) Process ID:/)) { $Applications{$Application}->{"$Hostname: The program $exe version $exever $msg"}++; next; } else { print "Application Hang: Cannot parse $ExpandedString\n"; } } elsif ($Application eq "AutoEnrollment") { #Ignore these - we don't run active directory next if $ExpandedString =~ /Automatic certificate enrollment for local system failed to contact the active directory/; } elsif ($Application =~ /^Group Policy/) { next if $ExpandedString =~ /This error was suppressed/; next if $ExpandedString =~ /could not apply .* The network path was not found/ and $Hostname =~ /$Laptops/i; } elsif ($Application =~ /Intel Alert/) { #Ignore these next if $ExpandedString =~ /Intel Alert Originator Manager loaded without security/; next if $ExpandedString =~ /Service Initialized Successfully/; } elsif ($Application =~ /LoadPerf/) { #Ignore these next if $ExpandedString =~ /Performance counters for the .* service were loaded successfully/; next if $ExpandedString =~ /Performance counters for the .* service were removed successfully/; } elsif ($Application =~ /NSCTOP/) { #Ignore these next if $ExpandedString =~ /Service started/; } elsif ($Application eq "Microsoft-Windows-CertificationAuthority") { next if $ExpandedString =~ /The Active Directory connection to .* has been reestablished to/; } elsif ($Application eq "Microsoft-Windows-Search") { next if $ExpandedString =~ /The content source .* cannot be accessed. Context: Application, SystemIndex Catalog Details: The object was not found/; } elsif ($Application eq "Microsoft-Windows-User Profiles Service") { if ( my ($program) = ($ExpandedString =~ /^Windows detected your registry file is still in use by other applications or services. The file will be unloaded now\..* Process \d+ \(\\Device\\.*\\(.*)\) has opened key .*/)) { next if $program =~ /$Ignore_profile_program/; } } elsif ($Application =~ /Norton Ghost/) { #Ignore these next if $ExpandedString =~ /Norton Ghost service started successfully/; next if $ExpandedString =~ /A scheduled baseline backup of .* completed successfully/; next if $ExpandedString =~ /A scheduled incremental backup of .* completed successfully/; } elsif ($Application =~ /SecurityCenter/) { #Ignore these - appears to be normal http://www.eventid.net/display.asp?eventid=1807&eventno=4468&source=SecurityCenter&phase=1 next if $ExpandedString =~ /The Security Center service has been stopped. It was prevented from running by a software group policy/; } elsif ($Application eq "SceCli") { next if $ExpandedString =~ /^Security policy cannot be propagated\. Cannot access the template\. Error code = 3\./ and $Hostname =~ /$Laptops/i; } elsif ($Application eq "ShadowProtectSPX") { next if $ExpandedString =~ /^Backup Finished/; next if $ExpandedString =~ /^Backup Failed .*\(\\\\.*The backup destination is not accessible/ and $Hostname =~ /$Laptops/i; } elsif ($Application =~ /SNARE/) { #Ignore these next if $ExpandedString =~ /The service was started/; next if $ExpandedString =~ /The service was stopped/; } elsif ($Application eq "SpeechRuntime") { next if $ExpandedString =~ /^Audio Orchestrator Power Event: Battery Saver Turned On, Voice Activation Disabled/; } elsif ($Application =~ /Symantec AntiVirus/) { #Ignore these next if $ExpandedString =~ /Symantec AntiVirus services startup was successful/; next if $ExpandedString =~ /Scan Complete: Risks: 0/; next if $ExpandedString =~ /Scan started on all drives and all extensions/; next if $ExpandedString =~ /Scan started on selected drives and folders and all extensions/; next if $ExpandedString =~ /Download of virus definition file from LiveUpdate server succeeded/; next if $ExpandedString =~ /Virus definitions are current/; next if $ExpandedString =~ /Could not scan \d+ files inside .* due to extraction errors encountered by the Decomposer Engines/; } elsif ($Application eq "openvpnserv") { next if $ExpandedString eq "The operation completed successfully."; } elsif ($Application =~ /cc.*Mgr/) { #Ignore these next if $ExpandedString =~ /service is starting/; next if $ExpandedString =~ /service has started/; } my $url = URI::URL->new("http://www.eventid.net/display.asp?eventid=$EventID&source=$Application"); my $urlstr = $url->abs; $Applications{$Application}->{"$Hostname: $ExpandedString\n$url"}++; } if (keys %Applications) { foreach my $Application (sort(keys %Applications)) { print "\n$Application\n"; foreach my $Error (sort(keys %{$Applications{$Application}})) { print " $Error : $Applications{$Application}->{$Error} Times\n"; } } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/mountd0000644000175000017500000001137515075123620020232 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ##################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':ip'; use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; #Init String Container my ( $IP, $Mount, $Name, $Number, ); #Init Array my @OtherList = (); #Init Hashes my ( %Attempted, %Mounted, %NotFound, %Rejected, %SignalExit ); while (defined(my $ThisLine = )) { if ( ($ThisLine =~ /^Unauthorized access by NFS client .*$/ ) or ($ThisLine =~ /^NFS client [^ ]+ tried to access .*$/ ) or ($ThisLine =~ /^[^ ]* exported to both [0-9.]*\/[0-5]* and [0-9.]*\/[0-9]/) ) { # don't care about this, as the next line reports the IP again } elsif ( ($IP,$Mount) = ($ThisLine =~ /^Blocked attempt of (\d+\.\d+\.\d+\.\d+) to mount (.*)$/) ) { $Name = LookupIP ($IP); $Mount = " " . $Mount; $Rejected{$Name}{$Mount}++; } elsif ( ($Name,$Mount) = ($ThisLine =~ /^refused mount request from (.+) for ([^ ]+)/) ) { $Mount = " " . $Mount; $Rejected{$Name}{$Mount}++; } elsif ( ($Mount) = ($ThisLine =~ /can.t stat exported dir (.*): No such file or directory/) ) { $Mount = " " . $Mount; $NotFound{$Mount}++; } elsif ( ($Mount,$IP) = ($ThisLine =~ /^NFS mount of (.*) attempted from (\d+\.\d+\.\d+\.\d+) $/) ) { $Name = LookupIP ($IP); $Mount = " " . $Mount; $Attempted{$Name}{$Mount}++; } elsif ( ($Name,$Mount) = ($ThisLine =~ /^authenticated (?:un)?mount request from (.+):\d+ for ([^ ]+)/) ) { $Mount = " " . $Mount; $Mounted{$Name}{$Mount}++; } elsif ( ($Name) = ($ThisLine =~ /^authenticated (?:un)?mount request from ([\w:]+)/) ) { $Mount = " unknown"; $Mounted{$Name}{$Mount}++; } elsif ( ($Mount,$IP) = ($ThisLine =~ /^(.*) has been mounted by (\d+\.\d+\.\d+\.\d+) $/) ) { $Name = LookupIP ($IP); $Mount = " " . $Mount; $Mounted{$Name}{$Mount}++; } elsif ( ($Number) = ($ThisLine =~ /Caught signal ([0-9]*), un-registering and exiting/) ) { $SignalExit{$Number}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } if (keys %Rejected) { print "\nRefused NFS mount attempts:\n"; foreach my $ThisOne (keys %Rejected) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$Rejected{$ThisOne}}) { print $ThatOne . ': ' . $Rejected{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %NotFound) { print "\nAttempts to mount nonexisting files or directories:\n"; foreach my $ThisOne (keys %NotFound) { print " " . $ThisOne .":" . $NotFound{$ThisOne} . " Time(s)\n"; } } if (keys %SignalExit) { printf "\nExit after catching signal:\n"; foreach my $Number (keys %SignalExit) { print " Signal " . $Number. ": " . $SignalExit{$Number} . " Time(s)\n"; } } if (($Detail >= 5) and (keys %Mounted)) { print "\nSuccessful NFS mounts:\n"; foreach my $ThisOne (keys %Mounted) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$Mounted{$ThisOne}}) { print $ThatOne . ': ' . $Mounted{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (($Detail >= 10) and (keys %Attempted)) { print "\nAttempted NFS mounts:\n"; foreach my $ThisOne (keys %Attempted) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$Attempted{$ThisOne}}) { print $ThatOne . ': ' . $Attempted{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/dnf-automatic0000644000175000017500000000434415075123620021455 0ustar logwatchlogwatch ######################################################### # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################### ######################################################### ## Copyright (c) 2024 Clive Lin ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my @DnfAutoLogs = (); my $DebugCounter = 0; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $always_show_dnf_last_check = $ENV{'always_show_dnf_last_check'} || 0; my $ThisLine; if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside DNF-AUTOMATIC Filter \n\n"; $DebugCounter = 1; } while (defined($ThisLine = )) { if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } # no special filter for "Last metadata expiration check: ..." # it seems to be the first and only one line # when Range=today or Range=yesterday # but when Range=all, it's a different situation. push @DnfAutoLogs,$ThisLine; } if ( ($#DnfAutoLogs eq 0) && ($always_show_dnf_last_check)) { # instead, check if there's only one line. # skip this line unless $always_show_dnf_last_check print @DnfAutoLogs; } elsif ($#DnfAutoLogs > 0) { # if more then one line, print all print @DnfAutoLogs; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/postfix0000644000175000017500000075673515122642254020442 0ustar logwatchlogwatch#!/usr/bin/perl ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ### All work since Dec 12, 2006 (logwatch CVS revision 1.28) ### Copyright (c) 2006-2012 Mike Cappella ### ### Covered under the included MIT/X-Consortium License: ### http://www.opensource.org/licenses/mit-license.php ### All modifications and contributions by other persons to ### this script are assumed to have been donated to the ### Logwatch project and thus assume the above copyright ### and licensing terms. If you want to make contributions ### under your own copyright or a different license this ### must be explicitly stated in the contribution an the ### Logwatch project reserves the right to not accept such ### contributions. If you have made significant ### contributions to this script and want to claim ### copyright please contact logwatch-devel@lists.sourceforge.net. ########################################################## ########################################################################## # The original postfix logwatch filter was written by # Kenneth Porter, and has had many contributors over the years. # # CVS log removed: see Changes file for postfix-logwatch at # http://logreporters.sourceforge.net/ # or included with the standalone postfix-logwatch distribution ########################################################################## ########################################################################## # # Test data included via inline comments starting with "#TD" # #use Devel::Size qw(size total_size); package Logreporters; use 5.008; use strict; use warnings; no warnings "uninitialized"; use re 'taint'; our $Version = '1.40.03'; our $progname_prefix = 'postfix'; # Specifies the default configuration file for use in standalone mode. my $config_file = "/usr/local/etc/${progname_prefix}-logwatch.conf"; # support postfix long (2.9+) or short queue ids my $re_QID_s = qr/[A-Z\d]+/; my $re_QID_l = qr/(?:NOQUEUE|[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ\d]+)/; our $re_QID; our $re_DSN = qr/(?:(?:\d{3})?(?: ?\d\.\d\.\d+)?)/; # postfix >= 3.11 will log TLS feature information in delivery status logging by default (tls=) our $re_DDD = qr/(?:(?:conn_use=\d+ )?delay=-?[\d.]+(?:, delays=[\d\/.]+)?(?:, tls=[^,]*)?(?:, dsn=[\d.]+)?)/; #MODULE: ../Logreporters/Utils.pm package Logreporters::Utils; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.003'; @ISA = qw(Exporter); @EXPORT = qw(&formathost &get_percentiles &get_percentiles2 &get_frequencies &commify &unitize &get_usable_sectvars &add_section &begin_section_group &end_section_group &get_version &unique_list); @EXPORT_OK = qw(&gen_test_log); } use subs qw (@EXPORT @EXPORT_OK); # Formats IP and hostname for even column spacing # sub formathost($ $) { # $_[0] : hostip # $_[1] : hostname; if (! $Logreporters::Config::Opts{'unknown'} and $_[1] eq 'unknown') { return $_[0]; } return sprintf "%-$Logreporters::Config::Opts{'ipaddr_width'}s %s", $_[0] eq '' ? '*unknown' : $_[0], $_[1] eq '' ? '*unknown' : lc $_[1]; } # Add a new section to the end of a section table # sub add_section($$$$$;$) { my $sref = shift; die "Improperly specified Section entry: $_[0]" if !defined $_[3]; my $entry = { CLASS => 'DATA', NAME => $_[0], DETAIL => $_[1], FMT => $_[2], TITLE => $_[3], }; $entry->{'DIVISOR'} = $_[4] if defined $_[4]; push @$sref, $entry; } { my $group_level = 0; # Begin a new section group. Groups can nest. # sub begin_section_group($;@) { my $sref = shift; my $group_name = shift; my $entry = { CLASS => 'GROUP_BEGIN', NAME => $group_name, LEVEL => ++$group_level, HEADERS => [ @_ ], }; push @$sref, $entry; } # Ends a section group. # sub end_section_group($;@) { my $sref = shift; my $group_name = shift; my $entry = { CLASS => 'GROUP_END', NAME => $group_name, LEVEL => --$group_level, FOOTERS => [ @_ ], }; push @$sref, $entry; } } # Generate and return a list of section table entries or # limiter key names, skipping any formatting entries. # If 'namesonly' is set, limiter key names are returned, # otherwise an array of section array records is returned. sub get_usable_sectvars(\@ $) { my ($sectref,$namesonly) = @_; my (@sect_list, %unique_names); foreach my $sref (@$sectref) { #print "get_usable_sectvars: $sref->{NAME}\n"; next unless $sref->{CLASS} eq 'DATA'; if ($namesonly) { $unique_names{$sref->{NAME}} = 1; } else { push @sect_list, $sref; } } # return list of unique names if ($namesonly) { return keys %unique_names; } return @sect_list; } # Print program and version info, preceeded by an optional string, and exit. # sub get_version() { print STDOUT "@_\n" if ($_[0]); print STDOUT "$Logreporters::progname: $Logreporters::Version\n"; exit 0; } # Returns a list of percentile values given a # sorted array of numeric values. Uses the formula: # # r = 1 + (p(n-1)/100) = i + d (Excel method) # # r = rank # p = desired percentile # n = number of items # i = integer part # d = decimal part # # Arg1 is an array ref to the sorted series # Arg2 is a list of percentiles to use sub get_percentiles(\@ @) { my ($aref,@plist) = @_; my ($n, $last, $r, $d, $i, @vals, $Yp); $last = $#$aref; $n = $last + 1; #printf "%6d" x $n . "\n", @{$aref}; #printf "n: %4d, last: %d\n", $n, $last; foreach my $p (@plist) { $r = 1 + ($p * ($n - 1) / 100.0); $i = int ($r); # integer part # domain: $i = 1 .. n if ($i == $n) { $Yp = $aref->[$last]; } elsif ($i == 0) { $Yp = $aref->[0]; print "CAN'T HAPPEN: $Yp\n"; } else { $d = $r - $i; # decimal part #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1 $Yp = $aref->[$i-1] + ($d * ($aref->[$i] - $aref->[$i-1])); } #printf "\np(%6.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d", $p, $r, $i, $d, $Yp; push @vals, $Yp; } return @vals; } sub get_num_scores($) { my $scoretab_r = shift; my $totalscores = 0; for (my $i = 0; $i < @$scoretab_r; $i += 2) { $totalscores += $scoretab_r->[$i+1] } return $totalscores; } # scoretab # # (score1, n1), (score2, n2), ... (scoreN, nN) # $i $i+1 # # scores are 0 based (0 = 1st score) sub get_nth_score($ $) { my ($scoretab_r, $n) = @_; my $i = 0; my $n_cur_scores = 0; #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"; while ($i < $#$scoretab_r) { #print "Samples_seen: $n_cur_scores\n"; $n_cur_scores += $scoretab_r->[$i+1]; if ($n_cur_scores >= $n) { #printf "range: %s %s %s\n", $i >= 2 ? $scoretab_r->[$i - 2] : '', $scoretab_r->[$i], $i+2 > $#$scoretab_r ? '' : $scoretab_r->[$i + 2]; #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]; return $scoretab_r->[$i]; } $i += 2; } print "returning last score $scoretab_r->[$i]\n"; return $scoretab_r->[$i]; } sub get_percentiles2(\@ @) { my ($scoretab_r, @plist) = @_; my ($n, $last, $r, $d, $i, @vals, $Yp); #$last = $#$scoretab_r - 1; $n = get_num_scores($scoretab_r); #printf "\n%6d" x $n . "\n", @{$scoretab_r}; #printf "\n\tn: %4d, @$scoretab_r\n", $n; foreach my $p (@plist) { ###print "\nPERCENTILE: $p\n"; $r = 1 + ($p * ($n - 1) / 100.0); $i = int ($r); # integer part if ($i == $n) { #print "last:\n"; #$Yp = $scoretab_r->[$last]; $Yp = get_nth_score($scoretab_r, $n); } elsif ($i == 0) { #$Yp = $scoretab_r->[0]; print "1st: CAN'T HAPPEN\n"; $Yp = get_nth_score($scoretab_r, 1); } else { $d = $r - $i; # decimal part #p = Y[i] + d(Y[i+1] - Y[i]), but since we're 0 based, use i=i-1 my $ithvalprev = get_nth_score($scoretab_r, $i); my $ithval = get_nth_score($scoretab_r, $i+1); $Yp = $ithvalprev + ($d * ($ithval - $ithvalprev)); } #printf "p(%6.2f), r: %6.2f, i: %6d, d: %6.2f, Yp: %6d\n", $p, $r, $i, $d, $Yp; push @vals, $Yp; } return @vals; } # Returns a list of frequency distributions given an incrementally sorted # set of sorted scores, and an incrementally sorted list of buckets # # Arg1 is an array ref to the sorted series # Arg2 is a list of frequency buckets to use sub get_frequencies(\@ @) { my ($aref,@blist) = @_; my @vals = ( 0 ) x (@blist); my @sorted_blist = sort { $a <=> $b } @blist; my $bucket_index = 0; OUTER: foreach my $score (@$aref) { #print "Score: $score\n"; for my $i ($bucket_index .. @sorted_blist - 1) { #print "\tTrying Bucket[$i]: $sorted_blist[$i]\n"; if ($score > $sorted_blist[$i]) { $bucket_index++; } else { #printf "\t\tinto Bucket[%d]\n", $bucket_index; $vals[$bucket_index]++; next OUTER; } } #printf "\t\tinto Bucket[%d]\n", $bucket_index - 1; $vals[$bucket_index - 1]++; } return @vals; } # Inserts commas in numbers for easier readability # sub commify ($) { return undef if ! defined ($_[0]); my $text = reverse $_[0]; $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; return scalar reverse $text; } # Unitize a number, and return appropriate printf formatting string # sub unitize($ $) { my ($num, $fmt) = @_; my $kilobyte = 2**10; my $megabyte = 2**20; my $gigabyte = 2**30; my $terabyte = 2**40; if ($num >= $terabyte) { $num /= $terabyte; $fmt .= '.3fT'; } elsif ($num >= $gigabyte) { $num /= $gigabyte; $fmt .= '.3fG'; } elsif ($num >= $megabyte) { $num /= $megabyte; $fmt .= '.3fM'; } elsif ($num >= $kilobyte) { $num /= $kilobyte; $fmt .= '.3fK'; } else { $fmt .= 'd '; } return ($num, $fmt); } # Returns a sublist of the supplied list of elements in an unchanged order, # where only the first occurrence of each defined element is retained # and duplicates removed # # Borrowed from amavis 2.6.2 # sub unique_list(@) { my ($r) = @_ == 1 && ref($_[0]) ? $_[0] : \@_; # accept list, or a list ref my (%seen); my (@unique) = grep { defined($_) && !$seen{$_}++ } @$r; return @unique; } # Generate a test maillog file from the '#TD' test data lines # The test data file is placed in /var/tmp/maillog.autogen # # arg1: "postfix" or "amavis" # arg2: path to postfix-logwatch or amavis-logwatch from which to read '#TD' data # # Postfix TD syntax: # TD() log entry # sub gen_test_log($) { my $scriptpath = shift; my $toolname = $Logreporters::progname_prefix; my $datafile = "/var/tmp/maillog-${toolname}.autogen"; die "gen_test_log: invalid toolname $toolname" if ($toolname !~ /^(postfix|amavis)$/); eval { require Sys::Hostname; require Fcntl; } or die "Unable to create test data file: required module(s) not found\n$@"; my $syslogtime = localtime; $syslogtime =~ s/^....(.*) \d{4}$/$1/; my ($hostname) = split /\./, Sys::Hostname::hostname(); # # avoid -T issues # delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; my $flags = &Fcntl::O_CREAT|&Fcntl::O_WRONLY|&Fcntl::O_TRUNC; sysopen(FH, $datafile, $flags) or die "Can't create test data file: $!"; print "Generating test log data file from $scriptpath: $datafile\n"; my $id; @ARGV = ($scriptpath); if ($toolname eq 'postfix') { my %services = ( DEF => 'smtpd', bQ => 'bounce', cN => 'cleanup', cQ => 'cleanup', lQ => 'local', m => 'master', p => 'pickup', pQ => 'pickup', ppQ => 'pipe', pfw => 'postfwd', pg => 'postgrey', pgQ => 'postgrey', ps => 'postsuper', qQ => 'qmgr', s => 'smtp', sQ => 'smtp', sd => 'smtpd', sdN => 'smtpd', sdQ => 'smtpd', spf => 'policy-spf', vN => 'virtual', vQ => 'virtual', ); $id = 'postfix/smtp[12345]'; while (<>) { if (/^\s*#TD([a-zA-Z]*[NQ]?)(\d+)?(?:\(([^)]+)\))? (.*)$/) { my ($service,$count,$qid,$line) = ($1, $2, $3, $4); #print "SERVICE: %s, QID: %s, COUNT: %s, line: %s\n", $service, $qid, $count, $line; if ($service eq '') { $service = 'DEF'; } die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service}); $id = $services{$service} . '[123]'; $id = 'postfix/' . $id unless $services{$service} eq 'postgrey'; #print "searching for service: \"$service\"\n\tFound $id\n"; if ($service =~ /N$/) { $id .= ': NOQUEUE'; } elsif ($service =~ /Q$/) { $id .= $qid ? $qid : ': DEADBEEF'; } $line =~ s/ +/ /g; $line =~ s/^ //g; #print "$syslogtime $hostname $id: \"$line\"\n" x ($count ? $count : 1); print FH "$syslogtime $hostname $id: $line\n" x ($count ? $count : 1); } } } else { #amavis my %services = ( DEF => 'amavis', dcc => 'dccproc', ); while (<>) { if (/^\s*#TD([a-z]*)(\d+)? (.*)$/) { my ($service,$count,$line) = ($1, $2, $3); if ($service eq '') { $service = 'DEF'; } die ("No such service: \"$service\": line \"$_\"") if (!exists $services{$service}); $id = $services{$service} . '[123]:'; if ($services{$service} eq 'amavis') { $id .= ' (9999-99)'; } print FH "$syslogtime $hostname $id $line\n" x ($count ? $count : 1) } } } close FH or die "Can't close $datafile: $!"; } 1; #MODULE: ../Logreporters/Config.pm package Logreporters::Config; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.002'; @ISA = qw(Exporter); @EXPORT = qw(&init_run_mode &add_option &get_options &init_cmdline &get_vars_from_file &process_limiters &process_debug_opts &init_getopts_table_common &zero_opts @Optspec %Opts %Configvars @Limiters %line_styles $fw1 $fw2 $sep1 $sep2 &D_CONFIG &D_ARGS &D_VARS &D_TREE &D_SECT &D_UNMATCHED &D_TEST &D_ALL ); } use subs @EXPORT; our @Optspec = (); # options table used by Getopts our %Opts = (); # program-wide options our %Configvars = (); # configuration file variables our @Limiters; # Report separator characters and widths our ($fw1,$fw2) = (22, 10); our ($sep1,$sep2) = ('=', '-'); use Getopt::Long; BEGIN { import Logreporters::Utils qw(&get_usable_sectvars); } our %line_styles = ( truncate => 0, wrap => 1, full => 2, ); sub init_run_mode($); sub confighash_to_cmdline(\%); sub get_vars_from_file(\% $); sub process_limiters(\@); sub add_option(@); sub get_options($); sub init_getopts_table_common(@); sub set_supplemental_reports($$); # debug constants sub D_CONFIG () { 1<<0 } sub D_ARGS () { 1<<1 } sub D_VARS () { 1<<2 } sub D_TREE () { 1<<3 } sub D_SECT () { 1<<4 } sub D_UNMATCHED () { 1<<5 } sub D_TEST () { 1<<30 } sub D_ALL () { 1<<31 } my %debug_words = ( config => D_CONFIG, args => D_ARGS, vars => D_VARS, tree => D_TREE, sect => D_SECT, unmatched => D_UNMATCHED, test => D_TEST, all => 0xffffffff, ); # Clears %Opts hash and initializes basic running mode options in # %Opts hash by setting keys: 'standalone', 'detail', and 'debug'. # Call early. # sub init_run_mode($) { my $config_file = shift; $Opts{'debug'} = 0; # Logwatch passes a filter's options via environment variables. # When running standalone (w/out logwatch), use command line options $Opts{'standalone'} = exists ($ENV{LOGWATCH_DETAIL_LEVEL}) ? 0 : 1; # Show summary section by default $Opts{'summary'} = 1; # Do not show mail queue section by default $Opts{'mailqueue'} = 0; if ($Opts{'standalone'}) { process_debug_opts($ENV{'LOGREPORTERS_DEBUG'}) if exists ($ENV{'LOGREPORTERS_DEBUG'}); } else { $Opts{'detail'} = $ENV{'LOGWATCH_DETAIL_LEVEL'}; # XXX #process_debug_opts($ENV{'LOGWATCH_DEBUG'}) if exists ($ENV{'LOGWATCH_DEBUG'}); } # first process --debug, --help, and --version options add_option ('debug=s', sub { process_debug_opts($_[1]); 1}); add_option ('version', sub { &Logreporters::Utils::get_version(); 1;}); get_options(1); # now process --config_file, so that all config file vars are read first add_option ('config_file|f=s', sub { get_vars_from_file(%Configvars, $_[1]); 1;}); get_options(1); # if no config file vars were read if ($Opts{'standalone'} and ! keys(%Configvars) and -f $config_file) { print "Using default config file: $config_file\n" if $Opts{'debug'} & D_CONFIG; get_vars_from_file(%Configvars, $config_file); } } sub get_options($) { my $pass_through = shift; #$SIG{__WARN__} = sub { print "*** $_[0]*** options error\n" }; # ensure we're called after %Opts is initialized die "get_options: program error: %Opts is empty" unless exists $Opts{'debug'}; my $p = new Getopt::Long::Parser; if ($pass_through) { $p->configure(qw(pass_through permute)); } else { $p->configure(qw(no_pass_through no_permute)); } #$p->configure(qw(debug)); if ($Opts{'debug'} & D_ARGS) { print "\nget_options($pass_through): enter\n"; printf "\tARGV(%d): ", scalar @ARGV; print @ARGV, "\n"; print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts; } if ($p->getoptions(\%Opts, @Optspec) == 0) { print STDERR "Use ${Logreporters::progname} --help for options\n"; exit 1; } if ($Opts{'debug'} & D_ARGS) { print "\t$_ ", defined $Opts{$_} ? "=> $Opts{$_}\n" : "\n" foreach sort keys %Opts; printf "\tARGV(%d): ", scalar @ARGV; print @ARGV, "\n"; print "get_options: exit\n"; } } sub add_option(@) { push @Optspec, @_; } # untaint string, borrowed from amavisd-new sub untaint($) { no re 'taint'; my ($str); if (defined($_[0])) { local($1); # avoid Perl taint bug: tainted global $1 propagates taintedness $str = $1 if $_[0] =~ /^(.*)$/; } return $str; } sub init_getopts_table_common(@) { my @supplemental_reports = @_; print "init_getopts_table_common: enter\n" if $Opts{'debug'} & D_ARGS; add_option ('help', sub { print STDOUT Logreporters::usage(undef); exit 0 }); add_option ('gen_test_log=s', sub { Logreporters::Utils::gen_test_log($_[1]); exit 0; }); add_option ('detail=i'); add_option ('nodetail', sub { # __none__ will set all limiters to 0 in process_limiters # since they are not known (Sections table is not yet built). push @Limiters, '__none__'; # 0 = disable supplemental_reports set_supplemental_reports(0, \@supplemental_reports); }); add_option ('max_report_width=i'); add_option ('summary!'); add_option ('mailqueue!'); add_option ('show_summary=i', sub { $Opts{'summary'} = $_[1]; 1; }); add_option ('show_mailqueue=i', sub { $Opts{'mailqueue'} = $_[1]; 1; }); # untaint ipaddr_width for use w/sprintf() in Perl v5.10 add_option ('ipaddr_width=i', sub { $Opts{'ipaddr_width'} = untaint ($_[1]); 1; }); add_option ('sect_vars!'); add_option ('show_sect_vars=i', sub { $Opts{'sect_vars'} = $_[1]; 1; }); add_option ('syslog_name=s'); add_option ('wrap', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; }); add_option ('full', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; }); add_option ('truncate', sub { $Opts{'line_style'} = $line_styles{$_[0]}; 1; }); add_option ('line_style=s', sub { my $style = lc($_[1]); my @list = grep (/^$style/, keys %line_styles); if (! @list) { print STDERR "Invalid line_style argument \"$_[1]\"\n"; print STDERR "Option line_style argument must be one of \"wrap\", \"full\", or \"truncate\".\n"; print STDERR "Use $Logreporters::progname --help for options\n"; exit 1; } $Opts{'line_style'} = $line_styles{lc($list[0])}; 1; }); add_option ('limit|l=s', sub { my ($limiter,$lspec) = split(/=/, $_[1]); if (!defined $lspec) { printf STDERR "Limiter \"%s\" requires value (ex. --limit %s=10)\n", $_[1],$_[1]; exit 2; } foreach my $val (split(/(?:\s+|\s*,\s*)/, $lspec)) { if ($val !~ /^\d+$/ and $val !~ /^(\d*)\.(\d+)$/ and $val !~ /^::(\d+)$/ and $val !~ /^:(\d+):(\d+)?$/ and $val !~ /^(\d+):(\d+)?:(\d+)?$/) { printf STDERR "Limiter value \"$val\" invalid in \"$limiter=$lspec\"\n"; exit 2; } } push @Limiters, lc $_[1]; }); print "init_getopts_table_common: exit\n" if $Opts{'debug'} & D_ARGS; } sub get_option_names() { my (@ret, @tmp); foreach (@Optspec) { if (ref($_) eq '') { # process only the option names my $spec = $_; $spec =~ s/=.*$//; $spec =~ s/([^|]+)\!$/$1|no$1/g; @tmp = split /[|]/, $spec; #print "PUSHING: @tmp\n"; push @ret, @tmp; } } return @ret; } # Set values for the configuration variables passed via hashref. # Variables are of the form ${progname_prefix}_KEYNAME. # # Because logwatch lowercases all config file entries, KEYNAME is # case-insensitive. # sub init_cmdline() { my ($href, $configvar, $value, $var); # logwatch passes all config vars via environment variables $href = $Opts{'standalone'} ? \%Configvars : \%ENV; # XXX: this is cheeze: need a list of valid limiters, but since # the Sections table is not built yet, we don't know what is # a limiter and what is an option, as there is no distinction in # variable names in the config file (perhaps this should be changed). my @valid_option_names = get_option_names(); die "Options table not yet set" if ! scalar @valid_option_names; print "confighash_to_cmdline: @valid_option_names\n" if $Opts{'debug'} & D_ARGS; my @cmdline = (); while (($configvar, $value) = each %$href) { if ($configvar =~ s/^${Logreporters::progname_prefix}_//o) { # distinguish level limiters from general options # would be easier if limiters had a unique prefix $configvar = lc $configvar; my $ret = grep (/^$configvar$/i, @valid_option_names); if ($ret == 0) { print "\tLIMITER($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS; push @cmdline, '-l', "$configvar" . "=$value"; } else { print "\tOPTION($ret): $configvar = $value\n" if $Opts{'debug'} & D_ARGS; unshift @cmdline, $value if defined ($value); unshift @cmdline, "--$configvar"; } } } unshift @ARGV, @cmdline; } # Obtains the variables from a logwatch-style .conf file, for use # in standalone mode. Returns an ENV-style hash of key/value pairs. # sub get_vars_from_file(\% $) { my ($href, $file) = @_; my ($var, $val); print "get_vars_from_file: enter: processing file: $file\n" if $Opts{'debug'} & D_CONFIG; my $message = undef; my $ret = stat ($file); if ($ret == 0) { $message = $!; } elsif (! -r _) { $message = "Permission denied"; } elsif ( -d _) { $message = "Is a directory"; } elsif (! -f _) { $message = "Not a regular file"; } if ($message) { print STDERR "Configuration file \"$file\": $message\n"; exit 2; } my $prog = $Logreporters::progname_prefix; open FILE, '<', "$file" or die "unable to open configuration file $file: $!"; while () { chomp; next if (/^\s*$/); # ignore all whitespace lines next if (/^\*/); # ignore logwatch's *Service lines next if (/^\s*#/); # ignore comment lines if (/^\s*\$(${prog}_[^=\s]+)\s*=\s*"?([^"]+)"?$/o) { ($var,$val) = ($1,$2); if ($val =~ /^(?:no|false)$/i) { $val = 0; } elsif ($val =~ /^(?:yes|true)$/i) { $val = 1; } elsif ($val eq '') { $var =~ s/${prog}_/${prog}_no/; $val = undef; } print "\t\"$var\" => \"$val\"\n" if $Opts{'debug'} & D_CONFIG; $href->{$var} = $val; } } close FILE or die "failed to close configuration handle for $file: $!"; print "get_vars_from_file: exit\n" if $Opts{'debug'} & D_CONFIG; } sub process_limiters(\@) { my ($sectref) = @_; my ($limiter, $var, $val, @errors); my @l = get_usable_sectvars(@$sectref, 1); if ($Opts{'debug'} & D_VARS) { print "process_limiters: enter\n"; print "\tLIMITERS: @Limiters\n"; } while ($limiter = shift @Limiters) { my @matched = (); printf "\t%-30s ",$limiter if $Opts{'debug'} & D_VARS; # disable all limiters when limiter is __none__: see 'nodetail' cmdline option if ($limiter eq '__none__') { $Opts{$_} = 0 foreach @l; next; } ($var,$val) = split /=/, $limiter; if ($val eq '') { push @errors, "Limiter \"$var\" requires value (ex. --limit limiter=10)"; next; } # try exact match first, then abbreviated match next if (scalar (@matched = grep(/^$var$/, @l)) == 1 or scalar (@matched = grep(/^$var/, @l)) == 1) { $limiter = $matched[0]; # unabbreviate limiter print "MATCH: $var: $limiter => $val\n" if $Opts{'debug'} & D_VARS; # XXX move limiters into section hash entry... $Opts{$limiter} = $val; next; } print "matched=", scalar @matched, ": @matched\n" if $Opts{'debug'} & D_VARS; push @errors, "Limiter \"$var\" is " . (scalar @matched == 0 ? "invalid" : "ambiguous: @matched"); } print "\n" if $Opts{'debug'} & D_VARS; if (@errors) { print STDERR "$_\n" foreach @errors; exit 2; } # Set the default value of 10 for each section if no limiter exists. # This allows output for each section should there be no configuration # file or missing limiter within the configuration file. foreach (@l) { $Opts{$_} = 10 unless exists $Opts{$_}; } # Enable collection for each section if a limiter is non-zero. foreach (@l) { #print "L is: $_\n"; #print "DETAIL: $Opts{'detail'}, OPTS: $Opts{$_}\n"; $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0; } #print "OPTS: \n"; map { print "$_ => $Opts{$_}\n"} keys %Opts; #print "COLLECTING: \n"; map { print "$_ => $Logreporters::TreeData::Collecting{$_}\n"} keys %Logreporters::TreeData::Collecting; } # Enable/disable supplemental reports # arg1: 0=off, 1=on # arg2,...: list of supplemental report keywords sub set_supplemental_reports($$) { my ($onoff,$aref) = @_; $Opts{$_} = $onoff foreach (@$aref); } sub process_debug_opts($) { my $optstring = shift; my @errors = (); foreach (split(/\s*,\s*/, $optstring)) { my $word = lc $_; my @matched = grep (/^$word/, keys %debug_words); if (scalar @matched == 1) { $Opts{'debug'} |= $debug_words{$matched[0]}; next; } if (scalar @matched == 0) { push @errors, "Unknown debug keyword \"$word\""; } else { # > 1 push @errors, "Ambiguous debug keyword abbreviation \"$word\": (matches: @matched)"; } } if (@errors) { print STDERR "$_\n" foreach @errors; print STDERR "Debug keywords: ", join (' ', sort keys %debug_words), "\n"; exit 2; } } # Zero the options controlling level specs and those # any others passed via Opts key. # # Zero the options controlling level specs in the # Detailed section, and set all other report options # to disabled. This makes it easy via command line to # disable the entire summary section, and then re-enable # one or more sections for specific reports. # # eg. progname --nodetail --limit forwarded=2 # sub zero_opts ($ @) { my $sectref = shift; # remaining args: list of Opts keys to zero map { $Opts{$_} = 0; print "zero_opts: $_ => 0\n" if $Opts{'debug'} & D_VARS;} @_; map { $Opts{$_} = 0 } get_usable_sectvars(@$sectref, 1); } 1; #MODULE: ../Logreporters/TreeData.pm package Logreporters::TreeData; use 5.008; use strict; use re 'taint'; use warnings; no warnings "uninitialized"; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.001'; @ISA = qw(Exporter); @EXPORT = qw(%Totals %Counts %Collecting $END_KEY); @EXPORT_OK = qw(&printTree &buildTree); } use subs @EXPORT_OK; BEGIN { import Logreporters::Config qw(%line_styles); } # Totals and Counts are the log line accumulator hashes. # Totals: maintains per-section grand total tallies for use in Summary section # Counts: is a multi-level hash, which maintains per-level key totals. our (%Totals, %Counts); # The Collecting hash determines which sections will be captured in # the Counts hash. Counts are collected only if a section is enabled, # and this hash obviates the need to test both existence and # non-zero-ness of the Opts{'keyname'} (either of which cause capture). # XXX The Opts hash could be used .... our %Collecting = (); sub buildTree(\% $ $ $ $ $); sub printTree($ $ $ $ $); =pod [ a:b:c, ... ] which would be interpreted as follows: a = show level a detail b = show at most b items at this level c = minimum count that will be shown =cut sub printTree($ $ $ $ $) { my ($treeref, $lspecsref, $line_style, $max_report_width, $debug) = @_; my ($entry, $line); my $cutlength = $max_report_width - 3; my $topn = 0; foreach $entry (sort bycount @$treeref) { ref($entry) ne "HASH" and die "Unexpected entry in tree: $entry\n"; #print "LEVEL: $entry->{LEVEL}, TOTAL: $entry->{TOTAL}, HASH: $entry, DATA: $entry->{DATA}\n"; # Once the top N lines have been printed, we're done if ($lspecsref->[$entry->{LEVEL}]{topn}) { if ($topn++ >= $lspecsref->[$entry->{LEVEL}]{topn} ) { print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n" unless ($debug) and do { $line = ' ' . ' ' x ($entry->{LEVEL} + 3) . '...'; printf "%-130s L%d: topn reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{topn}; }; last; } } # Once the item's count falls below the given threshold, we're done at this level # unless a top N is specified, as threshold has lower priority than top N elsif ($lspecsref->[$entry->{LEVEL}]{threshold}) { if ($entry->{TOTAL} <= $lspecsref->[$entry->{LEVEL}]{threshold}) { print ' ', ' ' x ($entry->{LEVEL} + 3), "...\n" unless ($debug) and do { $line = ' ' . (' ' x ($entry->{LEVEL} + 3)) . '...'; printf "%-130s L%d: threshold reached(%d)\n", $line, $entry->{LEVEL} + 1, $lspecsref->[$entry->{LEVEL}]{threshold}; }; last; } } $line = sprintf "%8d%s%s", $entry->{TOTAL}, ' ' x ($entry->{LEVEL} + 2), $entry->{DATA}; if ($debug) { printf "%-130s %-60s\n", $line, $entry->{DEBUG}; } # line_style full, or lines < max_report_width #printf "MAX: $max_report_width, LEN: %d, CUTLEN $cutlength\n", length($line); if ($line_style == $line_styles{'full'} or length($line) <= $max_report_width) { print $line, "\n"; } elsif ($line_style == $line_styles{'truncate'}) { print substr ($line,0,$cutlength), '...', "\n"; } elsif ($line_style == $line_styles{'wrap'}) { my $leader = ' ' x 8 . ' ' x ($entry->{LEVEL} + 2); print substr ($line, 0, $max_report_width, ''), "\n"; while (length($line)) { print $leader, substr ($line, 0, $max_report_width - length($leader), ''), "\n"; } } else { die ('unexpected line style'); } printTree ($entry->{CHILDREF}, $lspecsref, $line_style, $max_report_width, $debug) if (exists $entry->{CHILDREF}); } } 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/; # XXX optimize this using packed default sorting. Analysis shows speed isn't an issue though sub bycount { # Sort by totals, then IP address if one exists, and finally by data as a string local $SIG{__WARN__} = sub { print "*** PLEASE REPORT:\n*** $_[0]*** Unexpected: \"$a->{DATA}\", \"$b->{DATA}\"\n" }; $b->{TOTAL} <=> $a->{TOTAL} || pack('C4' => $a->{DATA} =~ /^$re_IP_strict/o) cmp pack('C4' => $b->{DATA} =~ /^$re_IP_strict/o) || $a->{DATA} cmp $b->{DATA} } # # Builds a tree of REC structures from the multi-key %Counts hashes # # Parameters: # Hash: A multi-key hash, with keys being used as category headings, and leaf data # being tallies for that set of keys # Level: This current recursion level. Call with 0. # # Returns: # Listref: A listref, where each item in the list is a rec record, described as: # DATA: a string: a heading, or log data # TOTAL: an integer: which is the subtotal of this item's children # LEVEL: an integer > 0: representing this entry's level in the tree # CHILDREF: a listref: references a list consisting of this node's children # Total: The cumulative total of items found for a given invocation # # Use the special key variable $END_KEY, which is "\a\a" (two ASCII bell's) to end a, # nested hash early, or the empty string '' may be used as the last key. our $END_KEY = "\a\a"; sub buildTree(\% $ $ $ $ $) { my ($href, $max_level_section, $levspecref, $max_level_global, $recurs_level, $show_unique, $debug) = @_; my ($subtotal, $childList, $rec); my @treeList = (); my $total = 0; foreach my $item (sort keys %$href) { if (ref($href->{$item}) eq "HASH") { #print " " x ($recurs_level * 4), "HASH: LEVEL $recurs_level: Item: $item, type: \"", ref($href->{$item}), "\"\n"; ($subtotal, $childList) = buildTree (%{$href->{$item}}, $max_level_section, $levspecref, $max_level_global, $recurs_level + 1, $debug); if ($recurs_level < $max_level_global and $recurs_level < $max_level_section) { # me + children $rec = { DATA => $item, TOTAL => $subtotal, LEVEL => $recurs_level, CHILDREF => $childList, }; if ($debug) { $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d", $recurs_level + 1, $max_level_global, $max_level_section, $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $subtotal; } push (@treeList, $rec); } } else { if ($item ne '' and $item ne $END_KEY and $recurs_level < $max_level_global and $recurs_level < $max_level_section) { $rec = { DATA => $item, TOTAL => $href->{$item}, LEVEL => $recurs_level, #CHILDREF => undef, }; if ($debug) { $rec->{DEBUG} = sprintf "L%d: levelspecs: %2d/%2d/%2d/%2d, Count: %10d", $recurs_level, $max_level_global, $max_level_section, $levspecref->[$recurs_level]{topn}, $levspecref->[$recurs_level]{threshold}, $href->{$item}; } push (@treeList, $rec); } $subtotal = $href->{$item}; } $total += $subtotal; } #print " " x ($recurs_level * 4), "LEVEL $recurs_level: Returning from recurs_level $recurs_level\n"; return ($total, \@treeList); } 1; #MODULE: ../Logreporters/RegEx.pm package Logreporters::RegEx; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.000'; @ISA = qw(Exporter); # @EXPORT = qw($re_IP); @EXPORT_OK = qw(); } # IPv4 and IPv6 # See syntax in RFC 2821 IPv6-address-literal, # eg. IPv6:2001:630:d0:f102:230:48ff:fe77:96e #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})'; # Modified from "dartware" case at http://forums.dartware.com/viewtopic.php?t=452# #our $re_IP = qr/(?:(?:(?:(?:[\da-f]{1,4}:){7}(?:[\da-f]{1,4}|:))|(?:(?:[\da-f]{1,4}:){6}(?::[\da-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[\da-f]{1,4}:){5}(?:(?:(?::[\da-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[\da-f]{1,4}:){4}(?:(?:(?::[\da-f]{1,4}){1,3})|(?:(?::[\da-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[\da-f]{1,4}:){3}(?:(?:(?::[\da-f]{1,4}){1,4})|(?:(?::[\da-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[\da-f]{1,4}:){2}(?:(?:(?::[\da-f]{1,4}){1,5})|(?:(?::[\da-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[\da-f]{1,4}:){1}(?:(?:(?::[\da-f]{1,4}){1,6})|(?:(?::[\da-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[\da-f]{1,4}){1,7})|(?:(?::[\da-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?)|(?:(?:\d{1,3}\.){3}(?:\d{1,3}))/i; # IPv4 only #our $re_IP = qr/(?:\d{1,3}\.){3}(?:\d{1,3})/; 1; #MODULE: ../Logreporters/Reports.pm package Logreporters::Reports; use 5.008; use strict; use re 'taint'; use warnings; no warnings "uninitialized"; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.002'; @ISA = qw(Exporter); @EXPORT = qw(&inc_unmatched &print_unmatched_report &print_percentiles_report2 &print_summary_report &print_detail_report); @EXPORT_OK = qw(); } use subs @EXPORT_OK; BEGIN { import Logreporters::Config qw(%Opts $fw1 $fw2 $sep1 $sep2 &D_UNMATCHED &D_TREE); import Logreporters::Utils qw(&commify &unitize &get_percentiles &get_percentiles2); import Logreporters::TreeData qw(%Totals %Counts &buildTree &printTree); } my (%unmatched_list); our $origline; # unmodified log line, for error reporting and debug sub inc_unmatched($) { my ($id) = @_; $unmatched_list{$origline}++; print "UNMATCHED($id): \"$origline\"\n" if $Opts{'debug'} & D_UNMATCHED; } # Print unmatched lines # sub print_unmatched_report() { return unless (keys %unmatched_list); print "\n\n**Unmatched Entries**\n"; foreach my $line (sort {$unmatched_list{$b}<=>$unmatched_list{$a} } keys %unmatched_list) { printf "%8d %s\n", $unmatched_list{$line}, $line; } } =pod ****** Summary ******************************************************** 2 Miscellaneous warnings 20621 Total messages scanned ---------------- 100.00% 662.993M Total bytes scanned 695,198,092 ======== ================================================ 19664 Ham ----------------------------------- 95.36% 19630 Clean passed 95.19% 34 Bad header passed 0.16% 942 Spam ---------------------------------- 4.57% 514 Spam blocked 2.49% 428 Spam discarded (no quarantine) 2.08% 15 Malware ------------------------------- 0.07% 15 Malware blocked 0.07% 1978 SpamAssassin bypassed 18 Released from quarantine 1982 Whitelisted 3 Blacklisted 12 MIME error 51 Bad header (debug supplemental) 28 Extra code modules loaded at runtime =cut # Prints the Summary report section # sub print_summary_report (\@) { my ($sections) = @_; my ($keyname,$cur_level); my @lines; my $expand_header_footer = sub { my $line = undef; foreach my $horf (@_) { # print blank line if keyname is newline if ($horf eq "\n") { $line .= "\n"; } elsif (my ($sepchar) = ($horf =~ /^(.)$/o)) { $line .= sprintf "%s %s\n", $sepchar x 8, $sepchar x 50; } else { die "print_summary_report: unsupported header or footer type \"$horf\""; } } return $line; }; if ($Opts{'detail'} >= 5) { my $header = "****** Summary "; print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n\n"; } my @headers; foreach my $sref (@$sections) { # headers and separators die "Unexpected Section $sref" if (ref($sref) ne 'HASH'); # Start of a new section group. # Expand and save headers to output at end of section group. if ($sref->{CLASS} eq 'GROUP_BEGIN') { $cur_level = $sref->{LEVEL}; $headers[$cur_level] = &$expand_header_footer(@{$sref->{HEADERS}}); } elsif ($sref->{CLASS} eq 'GROUP_END') { my $prev_level = $sref->{LEVEL}; # If this section had lines to output, tack on headers and footers, # removing extraneous newlines. if ($lines[$cur_level]) { # squish multiple blank lines if ($headers[$cur_level] and substr($headers[$cur_level],0,1) eq "\n") { if ( ! defined $lines[$prev_level][-1] or $lines[$prev_level][-1] eq "\n") { $headers[$cur_level] =~ s/^\n+//; } } push @{$lines[$prev_level]}, $headers[$cur_level] if $headers[$cur_level]; push @{$lines[$prev_level]}, @{$lines[$cur_level]}; my $f = &$expand_header_footer(@{$sref->{FOOTERS}}); push @{$lines[$prev_level]}, $f if $f; $lines[$cur_level] = undef; } $headers[$cur_level] = undef; $cur_level = $prev_level; } elsif ($sref->{CLASS} eq 'DATA') { # Totals data $keyname = $sref->{NAME}; if ($Totals{$keyname} > 0) { my ($numfmt, $desc, $divisor) = ($sref->{FMT}, $sref->{TITLE}, $sref->{DIVISOR}); my $fmt = '%8'; my $extra = ' %11s'; my $total = $Totals{$keyname}; # Z format provides unitized or unaltered totals, as appropriate if ($numfmt eq 'Z') { ($total, $fmt) = unitize ($total, $fmt); } else { $fmt .= "$numfmt "; } if ($divisor and $$divisor) { # XXX generalize this if (ref ($desc) eq 'ARRAY') { $desc = @$desc[0] . ' ' . @$desc[1] x (42 - 2 - length(@$desc[0])); } push @{$lines[$cur_level]}, sprintf "$fmt %-42s %6.2f%%\n", $total, $desc, $$divisor == $Totals{$keyname} ? 100.00 : $Totals{$keyname} * 100 / $$divisor; } else { push @{$lines[$cur_level]}, sprintf "$fmt %-37s $extra\n", $total, $desc, commify ($Totals{$keyname}); } } } else { die "print_summary_report: unexpected control..."; } } print @{$lines[0]}; print "\n"; } # Prints the Detail report section # # Note: side affect; deletes each key in Totals/Counts # after printout. Only the first instance of a key in # the Section table will result in Detail output. sub print_detail_report (\@) { my ($sections) = @_; my $header_printed = 0; return unless (keys %Counts); #use Devel::Size qw(size total_size); foreach my $sref ( @$sections ) { next unless $sref->{CLASS} eq 'DATA'; # only print detail for this section if DETAIL is enabled # and there is something in $Counts{$keyname} next unless $sref->{DETAIL}; next unless exists $Counts{$sref->{NAME}}; my $keyname = $sref->{NAME}; my $max_level = undef; my $print_this_key = 0; my @levelspecs = (); clear_level_specs($max_level, \@levelspecs); if (exists $Opts{$keyname}) { $max_level = create_level_specs($Opts{$keyname}, $Opts{'detail'}, \@levelspecs); $print_this_key = 1 if ($max_level); } else { $print_this_key = 1; } #print_level_specs($max_level,\@levelspecs); # at detail 5, print level 1, detail 6: level 2, ... #print STDERR "building: $keyname\n"; my ($count, $treeref) = buildTree (%{$Counts{$keyname}}, defined ($max_level) ? $max_level : 11, \@levelspecs, $Opts{'detail'} - 4, 0, $Opts{'debug'} & D_TREE); if ($count > 0) { if ($print_this_key) { my $desc = $sref->{TITLE}; $desc =~ s/^\s+//; if (! $header_printed) { my $header = "****** Detail ($max_level) "; print $header, '*' x ($Opts{'max_report_width'} - length $header), "\n"; $header_printed = 1; } printf "\n%8d %s %s\n", $count, $desc, $Opts{'sect_vars'} ? ('-' x ($Opts{'max_report_width'} - 18 - length($desc) - length($keyname))) . " [ $keyname ] -" : '-' x ($Opts{'max_report_width'} - 12 - length($desc)) } printTree ($treeref, \@levelspecs, $Opts{'line_style'}, $Opts{'max_report_width'}, $Opts{'debug'} & D_TREE); } #print STDERR "Total size Counts: ", total_size(\%Counts), "\n"; #print STDERR "Total size Totals: ", total_size(\%Totals), "\n"; $treeref = undef; $Totals{$keyname} = undef; delete $Totals{$keyname}; delete $Counts{$keyname}; } #print "\n"; } =pod Print out a standard percentiles report === Delivery Delays Percentiles =============================================================== 0% 25% 50% 75% 90% 95% 98% 100% ----------------------------------------------------------------------------------------------- Before qmgr 0.01 0.70 1.40 45483.70 72773.08 81869.54 87327.42 90966.00 In qmgr 0.00 0.00 0.00 0.01 0.01 0.01 0.01 0.01 Conn setup 0.00 0.00 0.00 0.85 1.36 1.53 1.63 1.70 Transmission 0.03 0.47 0.92 1.61 2.02 2.16 2.24 2.30 Total 0.05 1.18 2.30 45486.15 72776.46 81873.23 87331.29 90970.00 =============================================================================================== === Postgrey Delays Percentiles =========================================================== 0% 25% 50% 75% 90% 95% 98% 100% ------------------------------------------------------------------------------------------- Postgrey 727.00 727.00 727.00 727.00 727.00 727.00 727.00 727.00 =========================================================================================== tableref: data table: ref to array of arrays, first cell is label, subsequent cells are data title: table's title percentiles_str: string of space or comma separated integers, which are the percentiles calculated and output as table column data =cut sub print_percentiles_report2($$$) { my ($tableref, $title, $percentiles_str) = @_; return unless @$tableref; my $myfw2 = $fw2 - 1; my @percents = split /[ ,]/, $percentiles_str; # Calc y label width from the hash's keys. Each key is padded with the # string "#: ", # where # is a single-digit sort index. my $y_label_max_width = 0; for (@$tableref) { $y_label_max_width = length($_->[0]) if (length($_->[0]) > $y_label_max_width); } # Titles row my $col_titles_str = sprintf "%-${y_label_max_width}s" . "%${myfw2}s%%" x @percents , ' ', @percents; my $table_width = length($col_titles_str); # Table header row my $table_header_str = sprintf "%s %s ", $sep1 x 3, $title; $table_header_str .= $sep1 x ($table_width - length($table_header_str)); print "\n", $table_header_str; print "\n", $col_titles_str; print "\n", $sep2 x $table_width; my (@p, @coldata, @xformed); foreach (@$tableref) { my ($title, $ref) = ($_->[0], $_->[1]); #xxx my @sorted = sort { $a <=> $b } @{$_->[1]}; my @byscore = (); for my $bucket (sort { $a <=> $b } keys %$ref) { #print "Key: $title: Bucket: $bucket = $ref->{$bucket}\n"; # pairs: bucket (i.e. key), tally push @byscore, $bucket, $ref->{$bucket}; } my @p = get_percentiles2 (@byscore, @percents); printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), $title, @p; } =pod foreach (@percents) { #printf "\n%-${y_label_max_width}s" . "%${fw2}.2f" x scalar (@p), substr($title,3), @p; printf "\n%3d%%", $title; foreach my $val (@{shift @xformed}) { my $unit; if ($val > 1000) { $unit = 's'; $val /= 1000; } else { $unit = ''; } printf "%${fw3}.2f%-2s", $val, $unit; } } =cut print "\n", $sep1 x $table_width, "\n"; } sub clear_level_specs($ $) { my ($max_level,$lspecsref) = @_; #print "Zeroing $max_level rows of levelspecs\n"; $max_level = 0 if (not defined $max_level); for my $x (0..$max_level) { $lspecsref->[$x]{topn} = undef; $lspecsref->[$x]{threshold} = undef; } } # topn = 0 means don't limit # threshold = 0 means no min threshold sub create_level_specs($ $ $) { my ($optkey,$gdetail,$lspecref) = @_; return 0 if ($optkey eq "0"); my $max_level = $gdetail; # default to global detail level my (@specsP1, @specsP2, @specsP3); #printf "create_level_specs: key: %s => \"%s\", max_level: %d\n", $optkey, $max_level; foreach my $sp (split /[\s,]+/, $optkey) { #print "create_level_specs: SP: \"$sp\"\n"; # original level specifier if ($sp =~ /^\d+$/) { $max_level = $sp; #print "create_level_specs: max_level set: $max_level\n"; } # original level specifier + topn at level 1 elsif ($sp =~ /^(\d*)\.(\d+)$/) { if ($1) { $max_level = $1; } else { $max_level = $gdetail; } # top n specified, but no max level # force top N at level 1 (zero based) push @specsP1, { level => 0, topn => $2, threshold => 0 }; } # newer level specs elsif ($sp =~ /^::(\d+)$/) { push @specsP3, { level => undef, topn => 0, threshold => $1 }; } elsif ($sp =~ /^:(\d+):(\d+)?$/) { push @specsP2, { level => undef, topn => $1, threshold => defined $2 ? $2 : 0 }; } elsif ($sp =~ /^(\d+):(\d+)?:(\d+)?$/) { push @specsP1, { level => ($1 > 0 ? $1 - 1 : 0), topn => $2 ? $2 : 0, threshold => $3 ? $3 : 0 }; } else { print STDERR "create_level_specs: unexpected levelspec ignored: \"$sp\"\n"; } } #foreach my $sp (@specsP3, @specsP2, @specsP1) { # printf "Sorted specs: L%d, topn: %3d, threshold: %3d\n", $sp->{level}, $sp->{topn}, $sp->{threshold}; #} my ($min, $max); foreach my $sp ( @specsP3, @specsP2, @specsP1) { ($min, $max) = (0, $max_level); if (defined $sp->{level}) { $min = $max = $sp->{level}; } for my $level ($min..$max) { #printf "create_level_specs: setting L%d, topn: %s, threshold: %s\n", $level, $sp->{topn}, $sp->{threshold}; $lspecref->[$level]{topn} = $sp->{topn} if ($sp->{topn}); $lspecref->[$level]{threshold} = $sp->{threshold} if ($sp->{threshold}); } } return $max_level; } sub print_level_specs($ $) { my ($max_level,$lspecref) = @_; for my $level (0..$max_level) { printf "LevelSpec Row %d: %3d %3d\n", $level, $lspecref->[$level]{topn}, $lspecref->[$level]{threshold}; } } 1; #MODULE: ../Logreporters/RFC3463.pm package Logreporters::RFC3463; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.000'; @ISA = qw(Exporter); @EXPORT = qw(&get_dsn_msg); } use subs @EXPORT; #------------------------------------------------- # Enhanced Mail System Status Codes (aka: extended status codes) # # RFC 3463 http://www.ietf.org/rfc/rfc3463.txt # RFC 4954 http://www.ietf.org/rfc/rfc4954.txt # # Class.Subject.Detail # my %dsn_codes = ( class => { '2' => 'Success', '4' => 'Transient failure', '5' => 'Permanent failure', }, subject => { '0' => 'Other/Undefined status', '1' => 'Addressing status', '2' => 'Mailbox status', '3' => 'Mail system status', '4' => 'Network & routing status', '5' => 'Mail delivery protocol status', '6' => 'Message content/media status', '7' => 'Security/policy status', }, detail => { '0.0' => 'Other undefined status', '1.0' => 'Other address status', '1.1' => 'Bad destination mailbox address', '1.2' => 'Bad destination system address', '1.3' => 'Bad destination mailbox address syntax', '1.4' => 'Destination mailbox address ambiguous', '1.5' => 'Destination mailbox address valid', '1.6' => 'Mailbox has moved', '1.7' => 'Bad sender\'s mailbox address syntax', '1.8' => 'Bad sender\'s system address', '2.0' => 'Other/Undefined mailbox status', '2.1' => 'Mailbox disabled, not accepting messages', '2.2' => 'Mailbox full', '2.3' => 'Message length exceeds administrative limit.', '2.4' => 'Mailing list expansion problem', '3.0' => 'Other/Undefined mail system status', '3.1' => 'Mail system full', '3.2' => 'System not accepting network messages', '3.3' => 'System not capable of selected features', '3.4' => 'Message too big for system', '4.0' => 'Other/Undefined network or routing status', '4.1' => 'No answer from host', '4.2' => 'Bad connection', '4.3' => 'Routing server failure', '4.4' => 'Unable to route', '4.5' => 'Network congestion', '4.6' => 'Routing loop detected', '4.7' => 'Delivery time expired', '5.0' => 'Other/Undefined protocol status', '5.1' => 'Invalid command', '5.2' => 'Syntax error', '5.3' => 'Too many recipients', '5.4' => 'Invalid command arguments', '5.5' => 'Wrong protocol version', '5.6' => 'Authentication Exchange line too long', '6.0' => 'Other/Undefined media error', '6.1' => 'Media not supported', '6.2' => 'Conversion required & prohibited', '6.3' => 'Conversion required but not supported', '6.4' => 'Conversion with loss performed', '6.5' => 'Conversion failed', '7.0' => 'Other/Undefined security status', '7.1' => 'Delivery not authorized, message refused', '7.2' => 'Mailing list expansion prohibited', '7.3' => 'Security conversion required but not possible', '7.4' => 'Security features not supported', '7.5' => 'Cryptographic failure', '7.6' => 'Cryptographic algorithm not supported', '7.7' => 'Message integrity failure', }, # RFC 4954 complete => { '2.7.0' => 'Authentication succeeded', '4.7.0' => 'Temporary authentication failure', '4.7.12' => 'Password transition needed', '5.7.0' => 'Authentication required', '5.7.8' => 'Authentication credentials invalid', '5.7.9' => 'Authentication mechanism too weak', '5.7.11' => 'Encryption required for requested authentication mechanism', }, ); # Returns an RFC 3463 DSN messages given a DSN code # sub get_dsn_msg ($) { my $dsn = shift; my ($msg, $class, $subject, $detail); return "*DSN unavailable" if ($dsn =~ /^$/); unless ($dsn =~ /^(\d)\.((\d{1,3})\.\d{1,3})$/) { print "Error: not a DSN code $dsn\n"; return "Invalid DSN"; } $class = $1; $subject = $3; $detail = $2; #print "DSN: $dsn, Class: $class, Subject: $subject, Detail: $detail\n"; if (exists $dsn_codes{'class'}{$class}) { $msg = $dsn_codes{'class'}{$class}; } if (exists $dsn_codes{'subject'}{$subject}) { $msg .= ': ' . $dsn_codes{'subject'}{$subject}; } if (exists $dsn_codes{'complete'}{$dsn}) { $msg .= ': ' . $dsn_codes{'complete'}{$dsn}; } elsif (exists $dsn_codes{'detail'}{$detail}) { $msg .= ': ' . $dsn_codes{'detail'}{$detail}; } #print "get_dsn_msg: $msg\n" if ($msg); return $dsn . ': ' . $msg; } 1; #MODULE: ../Logreporters/PolicySPF.pm package Logreporters::PolicySPF; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.000'; @ISA = qw(Exporter); @EXPORT = qw(&postfix_policy_spf); } use subs @EXPORT; BEGIN { import Logreporters::TreeData qw(%Totals %Counts $END_KEY); import Logreporters::Utils; import Logreporters::Reports qw(&inc_unmatched); } # Handle postfix/policy_spf entries # # Mail::SPF::Result # Pass the SPF record designates the host to be allowed to send accept # Fail the SPF record has designated the host as NOT being allowed to send reject # SoftFail the SPF record has designated the host as NOT being allowed to send but is in transition accept but mark # Neutral the SPF record specifies explicitly that nothing can be said about validity accept # None the domain does not have an SPF record or the SPF record does not evaluate to a result accept # PermError a permanent error has occured (eg. badly formatted SPF record) unspecified # TempError a transient error has occured accept or reject sub postfix_policy_spf($) { my $line = shift; if ( $line =~ /^Attribute: / or # handler sender_policy_framework: is decisive. $line =~ /^handler [^:]+/ or # decided action=REJECT Please see http://www.openspf.org/why.html?sender=jrzjcez%40telecomitalia.it&ip=81.178.62.236&receiver=protegate1.zmi.at $line =~ /^decided action=/ or # pypolicyd-spf-0.7.1 # # Read line: "request=smtpd_access_policy" # Found the end of entry # 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'} # spfcheck: pyspf result: "['Pass', 'sender SPF authorized', 'helo']" # ERROR: Could not match line "#helo pass and mfrom none" # Traceback (most recent call last): # File "/usr/local/bin/policyd-spf", line 405, in # line = sys.stdin.readline() # KeyboardInterrupt $line =~ /^Read line: "/ or $line =~ /^Found the end of entry$/ or $line =~ /^Config: \{/ or $line =~ /^spfcheck: pyspf result/ or $line =~ /^Starting$/ or $line =~ /^Normal exit$/ or $line =~ /^ERROR: Could not match line/ or $line =~ /^Traceback / or $line =~ /^KeyboardInterrupt/ or $line =~ /^\s\s+/ ) { #print "IGNORING...\n\tORIG: $::OrigLine\n"; return } # Keep policy-spf warnings in its section if (my ($warn,$msg) = $line =~ /^warning: ([^:]+): (.*)$/) { #TDspf warning: ignoring garbage: # No SPF $msg =~ s/^# ?//; $Totals{'policyspf'}++; $Counts{'policyspf'}{'*Warning'}{ucfirst $warn}{$msg}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'}); return; } # pypolicyd-spf-0.7.1 # Fail; identity=helo; client-ip=192.168.0.1; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # Fail; identity=helo; client-ip=192.168.0.2; helo=example.com; envelope-from=<>; receiver=bogus@example.net # Neutral; identity=helo; client-ip=192.168.0.3; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # None; identity=helo; client-ip=192.168.0.4; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # None; identity=helo; client-ip=192.168.0.5; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # None; identity=mailfrom; client-ip=192.168.0.1; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # None; identity=mailfrom; client-ip=192.168.0.2; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # Pass; identity=helo; client-ip=192.168.0.2; helo=example.com; envelope-from=<>; receiver=bogus@example.net # Permerror; identity=helo; client-ip=192.168.0.4; helo=example.com; envelope-from=f@example.com; receiver=bogus2@example.net # Softfail; identity=mailfrom; client-ip=192.168.0.6; helo=example.com; envelope-from=f@example.com; receiver=yahl@example.org if ($line =~ /^(?:prepend Received-SPF: )?(Pass|Fail|None|Neutral|Permerror|Softfail|Temperror);? (.*)$/) { my $result = $1; my %params = $2 =~ /([-\w]+)=([^;]+)/g; #$params{'s'} = '*unknown' unless $params{'s'}; $Totals{'policyspf'}++; if ($Logreporters::TreeData::Collecting{'policyspf'}) { my ($id) = $params{'identity'}; $id =~ s/mailfrom/envelope-from/; $Counts{'policyspf'}{'Policy Action'}{"SPF: $result"}{join(': ',$params{'identity'},$params{$id})}{$params{'client-ip'}}{$params{'receiver'}}++; } return; } elsif ($line =~ /^ERROR /) { $line =~ s/^ERROR //; $Totals{'warningsother'}++; return unless ($Logreporters::TreeData::Collecting{'warningsother'}); $Counts{'warningsother'}{"$Logreporters::service_name: $line"}++; return; } # Strip QID if it exists, and trailing ": ", leaving just the message. $line =~ s/^(?:$Logreporters::re_QID|): //; # other ignored if ( $line =~ /^SPF \S+ \(.+?\): .*$/ or $line =~ /^Mail From/ or $line =~ /^:HELO check failed/ or # log entry has no space after : $line =~ /^testing:/ ) { #TDspf testing: stripped sender=jrzjcez@telecomitalia.it, stripped rcpt=hengstberger@adv.at # postfix-policyd-spf-perl-2.007 #TDspf SPF pass (Mechanism 'ip4:10.0.0.2/22' matched): Envelope-from: foo@example.com #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): Envelope-from: anyone@sample.net #TDspf SPF pass (Mechanism 'ip4:10.10.10.10' matched): HELO/EHLO (Null Sender): mailout2.example.com #TDspf SPF fail (Mechanism '-all' matched): HELO/EHLO: mailout1.example.com #TDspf SPF none (No applicable sender policy available): Envelope-from: efrom@example.com #TDspf SPF permerror (Included domain 'example.com' has no applicable sender policy): Envelope-from: efrom@example.com #TDspf SPF permerror (Maximum DNS-interactive terms limit (10) exceeded): Envelope-from: efrom@example.com #TDspf Mail From (sender) check failed - Mail::SPF->new(10.0.0.1, , test.DNSreport.com) failed: 'identity' option must not be empty #TDspf HELO check failed - Mail::SPF->new(, , ) failed: Missing required 'identity' option #TDspf SPF not applicable to localhost connection - skipped check #print "IGNORING...\n\tLINE: $line\n\tORIG: \"$Logreporters::Reports::origline\"\n"; return; } my ($action, $domain, $ip, $message, $mechanism); ($domain, $ip, $message, $mechanism) = ('*unknown', '*unknown', '', '*unavailable'); #print "LINE: '$line'\n"; # postfix-policyd-spf-perl: http://www.openspf.org/Software if ($line =~ /^Policy action=(.*)$/) { $line = $1; #: Policy action=DUNNO return if $line =~ /^DUNNO/; # Policy action=PREPEND X-Comment: SPF not applicable to localhost connection - skipped check return if $line =~ /^PREPEND X-Comment: SPF not applicable to localhost connection - skipped check$/; #print "LINE: '$line'\n"; if ($line =~ /^DEFER_IF_PERMIT SPF-Result=\[?(.*?)\]?: (.*) of .*$/) { my ($lookup,$message) = ($1,$2); # Policy action=DEFER_IF_PERMIT SPF-Result=[10.0.0.1]: Time-out on DNS 'SPF' lookup of '[10.0.0.1]' # Policy action=DEFER_IF_PERMIT SPF-Result=example.com: 'SERVFAIL' error on DNS 'SPF' lookup of 'example.com' $message =~ s/^(.*?) on (DNS SPF lookup)$/$2: $1/; $message =~ s/'//g; $Totals{'policyspf'}++; $Counts{'policyspf'}{'Policy Action'}{'defer_if_permit'}{$message}{$lookup}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'}); return; } if ($line =~ m{^550(?: \d\.\d\.\d+)? Please see http://www.openspf.(?:org|net)/Why\?(.*)$}) { # Policy action=550 Please see http://www.openspf.org/Why?s=mfrom&id=from%40example.com&ip=10.0.0.1&r=example.net # 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 # Policy action=550 Please see http://www.openspf.org/Why?id=someone%40example.com&ip=10.0.0.1&receiver=vps.example.net # Policy action=550 Please see http://www.openspf.net/Why?s=helo;id=example.com;ip=10.0.0.1;r=example.net my %params; for (split /[&;]/, $1) { my ($id,$val) = split /=/, $_; $params{$id} = $val; } $params{'id'} =~ s/^.*%40//; $params{'s'} = '*unknown' unless $params{'s'}; #print "Please see...:\n\tMessage: $message\n\tIP: $ip\n\tDomain: $domain\n"; $Totals{'policyspf'}++; $Counts{'policyspf'}{'Policy Action'}{'550 reject'}{'See http://www.openspf.org/Why?...'}{$params{'s'}}{$params{'ip'}}{$params{'id'}}++ if ($Logreporters::TreeData::Collecting{'policyspf'}); return; } if ($line =~ /^[^:]+: (none|pass|fail|softfail|neutral|permerror|temperror) \((.+?)\) receiver=[^;]+(?:; (.*))?$/) { # iehc is identity, envelope-from, helo, client-ip my ($result,$message,$iehc,$subject) = ($1,$2,$3,undef); my %params = (); #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 # Note: "identity=mailfrom" new in Mail::SPF version 2.006 Aug. 17 #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 #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 #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 #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 #TDspf Policy action=PREPEND Received-SPF: none (example.com: No applicable sender policy available) receiver=mx1.example #print "LINE: $iehc\n"; if ($iehc) { %params = $iehc =~ /([-\w]+)=([^;]+)/g; if (exists $params{'identity'}) { $params{'identity'} =~ s/identity=//; if ($params{'identity'} eq 'mfrom' or $params{'identity'} eq 'mailfrom') { $params{'identity'} = 'mail from'; } $params{'identity'} = uc $params{'identity'}; } $params{'envelope-from'} =~ s/"//g if exists $params{'envelope-from'}; #($helo = $params{'helo'}) =~ s/"//g if exists $params{'helo'}; $ip = $params{'client-ip'} if exists $params{'client-ip'}; } $message =~ s/^([^:]+): // and $subject = $1; if ($message =~ /^No applicable sender policy available$/) { $message = 'No sender policy'; } elsif ($message =~ s/^(Junk encountered in mechanism) '(.*?)'/$1/) { #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 $ip = formathost ($ip, 'mech: ' . $2); } elsif ($message =~ s/^(Included domain) '(.*?)' (has no .*)$/$1 $3/) { #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 $subject .= " (included: $2)"; } elsif ($message =~ /^Domain does not state whether sender is authorized to use '.*?' in '\S+' identity \(mechanism '(.+?)' matched\)$/) { # Domain does not state whether sender is authorized to use 'returns@example.com' in 'mfrom' identity (mechanism '?all' matched)) ($mechanism,$message) = ($1,'Domain does not state if sender authorized to use'); } elsif ($message =~ /^(\S+) is (not )?authorized( by default)? to use '.*?' in '\S+' identity(?:, however domain is not currently prepared for false failures)? \(mechanism '(.+?)' matched\)$/) { # 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)) # 192.168.1.10 is authorized by default to use 'from@example.com' in 'mfrom' identity (mechanism 'all' matched)) $message = join (' ', $1 eq 'Sender' ? 'Sender' : 'IP', # canonicalize IP address $2 ? 'not authorized' : 'authorized', $3 ? 'by default to use' : 'to use', ); $mechanism = $4; } elsif ($message =~ /^Maximum DNS-interactive terms limit \S+ exceeded$/) { $message = 'Maximum DNS-interactive terms limit exceeded'; } elsif ($message =~ /^Invalid IPv4 prefix length encountered in (.*)$/) { $subject .= " (invalid: $1)"; $message = 'Invalid IPv4 prefix length encountered'; } #print "Result: $result, Identity: $params{'identity'}, Mech: $mechanism, Subject: $subject, IP: $ip\n"; $Totals{'policyspf'}++; if ($Logreporters::TreeData::Collecting{'policyspf'}) { $message = join (' ', $message, $params{'identity'}) if exists $params{'identity'}; $Counts{'policyspf'}{'Policy Action'}{"SPF $result"}{$message}{'mech: ' .$mechanism}{$subject}{$ip}++ } return; } inc_unmatched('postfix_policy_spf(2)'); return; } =pod Mail::SPF::Query libmail-spf-query-perl 1:1.999 XXX incomplete Some possible smtp_comment results: pass "localhost is always allowed." none "SPF", "domain of sender $query->{sender} does not designate mailers unknown $explanation, "domain of sender $query->{sender} does not exist" $query->{spf_error_explanation}, $query->is_looping $query->{spf_error_explanation}, $directive_set->{hard_syntax_error} $query->{spf_error_explanation}, "Missing SPF record at $query->{domain}" error $query->{spf_error_explanation}, $query->{error} $result $explanation, $comment, $query->{directive_set}->{orig_txt} Possible header_comment results: pass "$query->{spf_source} designates $ip as permitted sender" fail "$query->{spf_source} does not designate $ip as permitted sender" softfail "transitioning $query->{spf_source} does not designate $ip as permitted sender" /^unknown / "encountered unrecognized mechanism during SPF processing of $query->{spf_source}" unknown "error in processing during lookup of $query->{sender}" neutral "$ip is neither permitted nor denied by domain of $query->{sender}" error "encountered temporary error during SPF processing of $query->{spf_source}" none "$query->{spf_source} does not designate permitted sender hosts" "could not perform SPF query for $query->{spf_source}" ); =cut #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 #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 #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 #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 #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 if (my ($result, $reply) = ($line =~ /^(SPF [^:]+): (.*)$/)) { #print "result: $result\n\treply: $reply\n\tORIG: \"$Logreporters::Reports::origline\"\n"; if ($reply =~ /^(?:smtp_comment=)(.*)$/) { $reply = $1; # SPF none if ($reply =~ /^SPF: domain of sender (?:(?:[^@]+@)?(\S+) )?does not designate mailers/) { $domain = $1 ? $1 : '*unknown'; #print "result: $result: domain: $domain\n"; } elsif ($reply =~ /^Please see http:\/\/[^\/]+\/why\.html\?sender=(?:.+%40)?([^&]+)&ip=([^&]+)/) { ($domain,$ip) = ($1,$2); #print "result: $result: domain: $domain, IP: $ip\n"; } # SPF unknown elsif ($reply =~ /^SPF record error: ([^,]+), .*: error in processing during lookup of (?:[^@]+\@)?(\S+)/) { ($message, $domain) = ($1, $2); #print "result: $result: domain: $domain, Problem: $message\n"; } elsif ($reply =~ /^SPF record error: ([^,]+), .*: encountered unrecognized mechanism during SPF processing of domain (?:[^@]+\@)?(\S+)/) { ($message, $domain) = ($1,$2); #print "result: \"$result\": domain: $domain, Problem: $message\n"; $result = "SPF permerror" if ($result =~ /SPF unknown mx-all/); } else { inc_unmatched('postfix_policy_spf(3)'); return; } } else { inc_unmatched('postfix_policy_spf(4)'); return; } $Totals{'policyspf'}++; if ($message) { $Counts{'policyspf'}{'Policy Action'}{$result}{$domain}{$ip}{$message}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'}); } else { $Counts{'policyspf'}{'Policy Action'}{$result}{$domain}{$ip}{$END_KEY}++ if ($Logreporters::TreeData::Collecting{'policyspf'}); } return; } inc_unmatched('postfix_policy_spf(5)'); } 1; #MODULE: ../Logreporters/Postfwd.pm package Logreporters::Postfwd; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.000'; @ISA = qw(Exporter); @EXPORT = qw(&postfix_postfwd); } use subs @EXPORT; BEGIN { import Logreporters::TreeData qw(%Totals %Counts $END_KEY); import Logreporters::Utils; import Logreporters::Reports qw(&inc_unmatched); } # postfwd: http://postfwd.org/ # # sub postfix_postfwd($) { my $line = shift; return if ( #TDpfw [STATS] Counters: 213000 seconds uptime, 39 rules #TDpfw [LOGS info]: compare rbl: "example.com[10.1.0.7]" -> "localrbl.local" #TDpfw [DNSBL] object 10.0.0.1 listed on rbl:list.dnswl.org (answer: 127.0.15.0, time: 0s) $line =~ /^\[STATS\] / or $line =~ /^\[DNSBL\] / or $line =~ /^\[LOGS info\]/ or $line =~ /^Process Backgrounded/ or $line =~ /^Setting [ug]id to/ or $line =~ /^Binding to TCP port/ or $line =~ /^terminating\.\.\./ or $line =~ /^Setting status interval to \S+ seconds/ or $line =~ /^postfwd .+ ready for input$/ or $line =~ /postfwd .+ (?:starting|terminated)/ ); my ($type,$rule,$id,$action,$host,$hostip,$recipient); if ($line =~ /^\[(RULES|CACHE)\] rule=(\d+), id=([^,]+), client=([^[]+)\[([^]]+)\], sender=.*?, recipient=<(.*?)>,.*? action=(.*)$/) { #TDpfw [RULES] rule=0, id=OK_DNSWL, client=example.com[10.0.0.1], sender=, recipient=, helo=, proto=ESMTP, state=RCPT, delay=0s, hits=OK_DNSWL, action=DUNNO #TDpfw [CACHE] rule=14, id=GREY_NODNS, client=unknown[192.168.0.1], sender=, recipient=, helo=, proto=ESMTP, state=RCPT, delay=0s, hits=SET_NODNS;EVAL_DNSBLS;EVAL_RHSBLS;GREY_NODNS, action=greylist ($type,$rule,$id,$host,$hostip,$recipient,$action) = ($1,$2,$3,$4,$5,$6,$7); $recipient = '*unknown' if (not defined $recipient); $Counts{'postfwd'}{"Rule $rule"}{$id}{$action}{$type}{$recipient}{formathost($hostip,$host)}++ if ($Logreporters::TreeData::Collecting{'postfwd'}); } elsif (($line =~ /Can't connect to TCP port/) or ($line =~ /Pid_file already exists for running process/) ) { $line =~ s/^[-\d\/:]+ //; # strip leading date/time stamps 2009/07/18-20:09:49 $Totals{'warningsother'}++; return unless ($Logreporters::TreeData::Collecting{'warningsother'}); $Counts{'warningsother'}{"$Logreporters::service_name: $line"}++; return; } # ignoring [DNSBL] lines #elsif ($line =~ /^\[DNSBL\] object (\S+) listed on (\S+) \(answer: ([^,]+), .*\)$/) { # #TDpfw [DNSBL] object 10.0.0.60 listed on rbl:list.dnswl.org (answer: 127.0.15.0, time: 0s) # ($type,$rbl) = split (/:/, $2); # $Counts{'postfwd'}{"DNSBL: $type"}{$rbl}{$1}{$3}{''}++ if ($Logreporters::TreeData::Collecting{'postfwd'}); #} else { inc_unmatched('postfwd'); return; } $Totals{'postfwd'}++; } 1; #MODULE: ../Logreporters/Postgrey.pm package Logreporters::Postgrey; use 5.008; use strict; use re 'taint'; use warnings; my (%pgDelays,%pgDelayso); BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.000'; @ISA = qw(Exporter); @EXPORT = qw(&postfix_postgrey &print_postgrey_reports); } use subs @EXPORT; BEGIN { import Logreporters::TreeData qw(%Totals %Counts $END_KEY); import Logreporters::Utils; import Logreporters::Config qw(%Opts); import Logreporters::Reports qw(&inc_unmatched &print_percentiles_report2); } # postgrey: http://postgrey.schweikert.ch/ # # Triplet: (client IP, envelope sender, envelope recipient address) # sub postfix_postgrey($) { my $line = shift; return if ( #TDpg cleaning up old logs... #TDpg cleaning up old entries... #TDpg cleaning clients database finished. before: 207, after: 207 #TDpg cleaning main database finished. before: 3800, after: 2539 #TDpg delayed 603 seconds: client=10.0.example.com, from=anyone@sample.net, to=joe@example.com #TDpg Setting uid to "504" #TDpg Setting gid to "1002 1002" #TDpg Process Backgrounded #TDpg 2008/03/08-15:54:49 postgrey (type Net::Server::Multiplex) starting! pid(21961) #TDpg Binding to TCP port 10023 on host 127.0.0.1 #TDpg 2007/01/25-14:58:24 Server closing! #TDpg Couldn't unlink "/var/run/postgrey.pid" [Permission denied] #TDpg ignoring garbage: #TDpg unrecognized request type: '' #TDpg rm /var/spool/postfix/postgrey/log.0000000002 #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 #TDpg Resolved [localhost]:10023 to [127.0.0.1]:10023, IPv4 $line =~ /^cleaning / or $line =~ /^delayed / or $line =~ /^cleaning / or $line =~ /^Setting [ug]id/ or $line =~ /^Process Backgrounded/ or $line =~ /^Binding to / or $line =~ /^Couldn't unlink / or $line =~ /^ignoring garbage: / or $line =~ /^unrecognized request type/ or $line =~ /^rm / or # unanchored last $line =~ /Pid_file already exists/ or $line =~ /postgrey .* starting!/ or $line =~ /Server closing!/ or $line =~ /Resolved .*localhost.*IPv4/ ); my ($action,$reason,$delay,$host,$ip,$sender,$recip); if ($line =~ /^(?:$Logreporters::re_QID: )?action=(.*?), reason=(.*?)(?:, delay=(\d+))?, client_name=(.*?), client_address=(.*?)(?:, sender=(.*?))?(?:, +recipient=(.*))?$/o) { #TDpg action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net #TDpgQ action=greylist, reason=new, client_name=example.com, client_address=10.0.0.1, sender=from@example.com #TDpgQ action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net #TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, sender=from@example.com, recipient=to@sample.net #TDpg action=pass, reason=triplet found, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net #TDpg action=pass, reason=triplet found, delay=99, client_name=example.com, client_address=10.0.0.1, recipient=to@sample.net ($action,$reason,$delay,$host,$ip,$sender,$recip) = ($1,$2,$3,$4,$5,$6,$7); $reason =~ s/^(early-retry) \(.* missing\)$/$1/; $recip = '*unknown' if (not defined $recip); $sender = '' if (not defined $sender); $Totals{'postgrey'}++; if ($Logreporters::TreeData::Collecting{'postgrey'}) { $Counts{'postgrey'}{"\u$action"}{"\u$reason"}{formathost($ip,$host)}{$recip}{$sender}++; if (defined $delay and $Logreporters::TreeData::Collecting{'postgrey_delays'}) { $pgDelays{'1: Total'}{$delay}++; push @{$pgDelayso{'Postgrey'}}, $delay } } } elsif ($line =~ /^whitelisted: (.*?)(?:\[([^]]+)\])?$/) { #TDpg: whitelisted: example.com[10.0.0.1] $Totals{'postgrey'}++; if ($Logreporters::TreeData::Collecting{'postgrey'}) { $Counts{'postgrey'}{'Whitelisted'}{defined $2 ? formathost($2,$1) : $1}{$END_KEY}++; } } elsif ($line =~ /^tarpit whitelisted: (.*?)(?:\[([^]]+)\])?$/) { #TDpg: tarpit whitelisted: example.com[10.0.0.1] $Totals{'postgrey'}++; if ($Logreporters::TreeData::Collecting{'postgrey'}) { $Counts{'postgrey'}{'Tarpit whitelisted'}{defined $2 ? formathost($2,$1) : $1}{$END_KEY}++; } } else { inc_unmatched('postgrey'); } return; } sub print_postgrey_reports() { #print STDERR "pgDelays memory usage: ", commify(Devel::Size::total_size(\%pgDelays)), "\n"; if ($Opts{'postgrey_delays'}) { my @table; for (sort keys %pgDelays) { # anon array ref: label, array ref of $Delay{key} push @table, [ substr($_,3), $pgDelays{$_} ]; } if (@table) { print_percentiles_report2(\@table, "Postgrey Delays Percentiles", $Opts{'postgrey_delays_percentiles'}); } } } 1; #MODULE: ../Logreporters/PolicydWeight.pm package Logreporters::PolicydWeight; use 5.008; use strict; use re 'taint'; use warnings; BEGIN { use Exporter (); use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '1.000'; @ISA = qw(Exporter); @EXPORT = qw(&postfix_policydweight); } use subs @EXPORT; BEGIN { import Logreporters::Reports qw(&inc_unmatched); import Logreporters::TreeData qw(%Totals %Counts); import Logreporters::Utils; } # Handle postfix/policydweight entries # sub postfix_policydweight($) { my $line = shift; my ($r1, $code, $reason, $reason2); if ( $line =~ /^weighted check/ or $line =~ /^policyd-weight .* started and daemonized/ or $line =~ /^(cache|child|master): / or $line =~ /^cache (?:spawned|killed)/ or $line =~ /^child \d+ exited/ or $line =~ /^Daemon terminated/ or $line =~ /^Daemon terminated/ ) { #print "$OrigLine\n"; return; } if ($line =~ s/^decided action=//) { $line =~ s/; delay: \d+s$//; # ignore, eg.: "delay: 3s" #print "....\n\tLINE: $line\n\tORIG: '$Logreporters::Reports::origline'\n"; if (($code,$r1) = ($line =~ /^(\d+)\s+(.*)$/ )) { my @problems = (); for (split /; */, $r1) { 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/ ) { push @problems, 'spam/forged: bad DNS/hit DNSRBLs'; } elsif (/^Your MTA is listed in too many DNSBLs/) { push @problems, 'too many DNSBLs'; } elsif (/^temporarily blocked because of previous errors - retrying too fast\. penalty: \d+ seconds x \d+ retries\./) { push @problems, 'temp blocked: retrying too fast'; } elsif (/^Please use DynDNS/) { push @problems, 'use DynDNS'; } elsif (/^please relay via your ISP \([^)]+\)/) { push @problems, 'use ISP\'s relay'; } elsif (/^in (.*)/) { push @problems, $1; } elsif (m#^check http://rbls\.org/\?q=#) { push @problems, 'see http://rbls.org'; } elsif (/^MTA helo: .* \(helo\/hostname mismatch\)/) { push @problems, 'helo/hostname mismatch'; } elsif (/^No DNS entries for your MTA, HELO and Domain\. Contact YOUR administrator\s+/) { push @problems, 'no DNS entries'; } else { push @problems, $_; } } $reason = $code; $reason2 = join (', ', @problems); } elsif ($line =~ s/^DUNNO\s+//) { #decided action=DUNNO multirecipient-mail - already accepted by previous query; delay: 0s $reason = 'DUNNO'; $reason2 = $line; } elsif ($line =~ s/^check_greylist//) { #decided action=check_greylist; delay: 16s $reason = 'Check greylist'; $reason2 = $line; } elsif ($line =~ s/^PREPEND X-policyd-weight:\s+//) { #decided action=PREPEND X-policyd-weight: using cached result; rate: -7.6; delay: 0s if ($line =~ /(using cached result); rate:/) { $reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = "\u$1"; } else { #decided action=PREPEND X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 P0F_LINUX=0 , rate: -7.6; delay: 2s $reason = 'PREPEND X-policyd-weight: mail accepted'; $reason2 = 'Varies'; } } else { return; } } elsif ($line =~ /^err/) { # coerce policyd-weight err's into general warnings $Totals{'startuperror'}++; $Counts{'startuperror'}{'Service: policyd-weight'}{$line}++ if ($Logreporters::TreeData::Collecting{'startuperror'}); return; } else { inc_unmatched('policydweight'); return; } $Totals{'policydweight'}++; $Counts{'policydweight'}{$reason}{$reason2}++ if ($Logreporters::TreeData::Collecting{'policydweight'}); } 1; package Logreporters; BEGIN { import Logreporters::Utils; import Logreporters::Config; import Logreporters::TreeData qw(%Totals %Counts %Collecting printTree buildTree $END_KEY); import Logreporters::RegEx; import Logreporters::Reports; import Logreporters::RFC3463; import Logreporters::PolicySPF; import Logreporters::Postfwd; import Logreporters::Postgrey; import Logreporters::PolicydWeight; } use 5.008; use strict; use warnings; no warnings "uninitialized"; use re 'taint'; use File::Basename; our $progname = fileparse($0); my @supplemental_reports = qw(delays postgrey_delays); # Default values for various options. These are used # to reset default values after an option has been # disabled (via undef'ing its value). This allows # a report to be disabled via config file or --nodetail, # but reenabled via subsequent command line option my %Defaults = ( detail => 10, # report level detail max_report_width => 100, # maximum line width for report output line_style => undef, # lines > max_report_width, 0=truncate,1=wrap,2=full syslog_name => 'postfix', # service name (postconf(5), syslog_name) sect_vars => 0, # show section vars in detail report hdrs unknown => 1, # show 'unknown' in address/hostname pairs ipaddr_width => 15, # width for printing ip addresses long_queue_ids => 0, # enable long queue ids (2.9+) delays => 1, # show message delivery delays report delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in delays report reject_reply_patterns => '5.. 4.. warn', # reject reply grouping patterns postgrey_delays => 1, # show postgrey delays report postgrey_delays_percentiles => '0 25 50 75 90 95 98 100', # percentiles shown in postgrey delays report ); my $usage_str = <<"END_USAGE"; Usage: $progname [ ARGUMENTS ] [logfile ...] ARGUMENTS can be one or more of options listed below. Later options override earlier ones. Any argument may be abbreviated to an unambiguous length. Input is read from the named logfile(s), or STDIN. --debug AREAS provide debug output for AREAS --help print usage information --version print program version --config_file FILE, -f FILE use alternate configuration file FILE --ignore_services PATTERN ignore postfix/PATTERN services --syslog_name PATTERN only consider log lines that match syslog service name PATTERN --detail LEVEL print LEVEL levels of detail (default: 10) --nodetail set all detail levels to 0 --[no]summary display the summary section --[no]mailqueue display the mail queue section --ipaddr_width WIDTH use WIDTH chars for IP addresses in address/hostname pairs --line_style wrap|full|truncate disposition of lines > max_report_width (default: truncate) --full same as --line_style=full --truncate same as --line_style=truncate --wrap same as --line_style=wrap --max_report_width WIDTH limit report width to WIDTH chars (default: 100) --limit L=V, -l L=V set level limiter L with value V --[no]long_queue_ids use long queue ids --[no]unknown show the 'unknown' hostname in formatted address/hostnames pairs --[no]sect_vars [do not] show config file var/cmd line option names in section titles --recipient_delimiter C split delivery addresses using recipient delimiter char C --reject_reply_patterns "R1 [R2 ...]" set reject reply patterns used in to group rejects to R1, [R2 ...], where patterns are [45][.0-9][.0-9] or "Warn" (default: 5.. 4.. Warn) Supplemental reports --[no]delays [do not] show msg delays percentiles report --delays_percentiles "P1 [P2 ...]" set delays report percentiles to P1 [P2 ...] (range: 0...100) --[no]postgrey_delays [do not] show postgrey delays percentiles report --postgrey_delays_percentiles "P1 [P2 ...]" set postgrey delays report percentiles to P1 [P2 ...] (range: 0...100) END_USAGE my @RejectPats; # pattern list used to match against reject replys my @RejectKeys; # 1-to-1 with RejectPats, but with 'x' replacing '.' (for report output) my (%DeferredByQid, %SizeByQid, %AcceptedByQid, %Delays); # local prototypes sub usage; sub init_getopts_table; sub init_defaults; sub build_sect_table; sub postfix_bounce; sub postfix_cleanup; sub postfix_panic; sub postfix_fatal; sub postfix_error; sub postfix_warning; sub postfix_script; sub backwards_compatible; sub postfix_postsuper; sub process_delivery_attempt; sub cleanhostreply; sub strip_ftph; sub get_reject_key; sub expand_bare_reject_limiters; sub create_ignore_list; sub in_ignore_list; sub header_body_checks; sub milter_common; # lines that match any RE in this list will be ignored. # see create_ignore_list(); my @ignore_list = (); # The Sections table drives Summary and Detail reports. For each entry in the # table, if there is data available, a line will be output in the Summary report. # Additionally, a sub-section will be output in the Detail report if both the # global --detail, and the section's limiter variable, are sufficiently high (a # non-existent section limiter variable is considered to be sufficiently high). # my @Sections = (); # List of reject variants. See also: "Add reject variants" below, and conf file(s). my @RejectClasses = qw( rejectrelay rejecthelo rejectdata rejectunknownuser rejectrecip rejectsender rejectclient rejectunknownclient rejectunknownreverseclient rejectunverifiedclient rejectrbl rejectheader rejectbody rejectcontent rejectsize rejectmilter rejectproxy rejectinsufficientspace rejectconfigerror rejectverify rejectetrn rejectlookupfailure ); # Dispatch table of the list of supported policy services # XXX have add-ins register into the dispatch table my @policy_services = ( [ qr/^postfwd/, \&Logreporters::Postfwd::postfix_postfwd ], [ qr/^postgrey/, \&Logreporters::Postgrey::postfix_postgrey ], [ qr/^policyd?-spf/, \&Logreporters::PolicySPF::postfix_policy_spf ], [ qr/^policyd-?weight/, \&Logreporters::PolicydWeight::postfix_policydweight ], ); # Initialize main running mode and basic opts init_run_mode($config_file); # Configure the Getopts options table init_getopts_table(); # Place configuration file/environment variables onto command line init_cmdline(); # Initialize default values init_defaults(); # Process command line arguments, 0=no_permute,no_pass_through get_options(0); # Build the Section table, after reject_reply_patterns is final build_sect_table(); # Expand bare rejects before generic processing expand_bare_reject_limiters(); # Run through the list of Limiters, setting the limiters in %Opts. process_limiters(@Sections); # Set collection for any enabled supplemental sections foreach (@supplemental_reports) { $Logreporters::TreeData::Collecting{$_} = (($Opts{'detail'} >= 5) && $Opts{$_}) ? 1 : 0; } if (! defined $Opts{'line_style'}) { # default line style to full if detail >= 11, or truncate otherwise $Opts{'line_style'} = ($Opts{'detail'} > 10) ? $line_styles{'full'} : $line_styles{'truncate'}; } # Set the QID RE to capture either pre-2.9 short style or 2.9+ long style. $re_QID = $Opts{'long_queue_ids'} ? $re_QID_l : $re_QID_s; # Create the list of REs used to match against log lines create_ignore_list(); # Notes: # # - IN REs, always use /o flag or qr// at end of RE when RE uses interpolated vars # - In REs, email addresses may be empty "<>" - capture using *, not + ( eg. from=<.*?> ) # - See additional notes below, search for "Note:". # - XXX indicates change, fix or thought required # Main processing loop # LINE: while ( <> ) { chomp; s/\s+$//; next unless length $_; $Logreporters::Reports::origline = $_; # Linux: Jul 1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1] # FreeBSD: Jul 1 20:08:06 mailhost postfix/smtpd[4379]: connect from unknown[10.0.0.1] # Aug 17 15:16:12 mailhost postfix/cleanup[14194]: [ID 197553 mail.info] EC2B339E5: message-id=<2616.EC2B339E5@example.com> # Dec 25 05:20:28 mailhost policyd-spf[14194]: [ID 27553 mail.info] ... policyd-spf stuff ... 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; our $service_name = $3; my ($mailhost,$server_name,$p1) = ($1,$2,$4); #print "mailhost: $mailhost, servername: $server_name, servicename: $service_name, p1: $p1\n"; $service_name = $server_name unless $service_name; #print "service_name: $service_name\n"; # ignored postfix services... next if $service_name eq 'postlog'; next if $service_name =~ /^$Opts{'ignore_services'}$/o; next if $p1 =~ /^[A-Z0-9]+: passing <[^>]+> to transport=lmtp$/; # common log entries up front if ($p1 =~ s/^connect from //) { #TD25 connect from sample.net[10.0.0.1] #TD connect from mail.example.com[2001:dead:beef::1] #TD connect from localhost.localdomain[127.0.0.1] #TD connect from unknown[unknown] $Totals{'connectioninbound'}++; next unless ($Collecting{'connectioninbound'}); my $host = $p1; my $hostip; if (($host,$hostip) = ($host =~ /^([^[]+)\[([^]]+)\]/)) { $host = formathost($hostip,$host); } $Counts{'connectioninbound'}{$host}++; next; } if ($p1 =~ /^disconnect from /) { #TD25 disconnect from sample.net[10.0.0.1] #TD disconnect from mail.example.com[2001:dead:beef::1] $Totals{'disconnection'}++; next; } if ($p1 =~ s/^connect to //) { next if ($p1 =~ /^subsystem /); $Totals{'connecttofailure'}++; next unless ($Collecting{'connecttofailure'}); my ($host,$hostip,$reason,$port) = ($p1 =~ /^([^[]+)\[([^]]+)\](?::\d+)?: (.*?)(?:\s+\(port (\d+)\))?$/); # all "connect to" messages indicate a problem with the connection #TDs connect to example.org[10.0.0.1]: Connection refused (port 25) #TDs connect to mail.sample.com[10.0.0.1]: No route to host (port 25) #TDs connect to sample.net[192.168.0.1]: read timeout (port 25) #TDs connect to mail.example.com[10.0.0.1]: server dropped connection without sending the initial SMTP greeting (port 25) #TDs connect to mail.example.com[192.168.0.1]: server dropped connection without sending the initial SMTP greeting (port 25) #TDs connect to ipv6-1.example.com[2001:dead:beef::1]: Connection refused (port 25) #TDs connect to ipv6-2.example.com[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]: Connection refused (port 25) #TDs connect to ipv6-3.example.com[1080:0:0:0:8:800:200C:4171]: Connection refused (port 25) #TDs connect to ipv6-4.example.com[3ffe:2a00:100:7031::1]: Connection refused (port 25) #TDs connect to ipv6-5.example.com[1080::8:800:200C:417A]: Connection refused (port 25) #TDs connect to ipv6-6.example.com[::192.9.5.5]: Connection refused (port 25) #TDs connect to ipv6-7.example.com[::FFFF:129.144.52.38]: Connection refused (port 25) #TDs connect to ipv6-8.example.com[2010:836B:4179::836B:4179]: Connection refused (port 25) #TDs connect to mail.example.com[10.0.0.1]: server refused to talk to me: 452 try later (port 25) $host = join(' :', $host, $port) if ($port and $port ne '25'); # Note: See ConnectToFailure below if ($reason =~ /^server (refused to talk to me): (.*)$/) { $Counts{'connecttofailure'}{ucfirst($1)}{formathost($hostip,$host)}{$2}++; } else { $Counts{'connecttofailure'}{ucfirst($reason)}{formathost($hostip,$host)}{''}++; } next; } =pod real 3m43.997s user 3m39.038s sys 0m3.005s =pod # Handle before panic, fatal, warning, so that service-specific code gets first crack # XXX replace w/dispatch table for add-ins, so user's can add their own... if ($service_name eq 'postfwd') { postfix_postfwd($p1); next; } if ($service_name eq 'postgrey') { postfix_postgrey($p1); next; } if ($service_name =~ /^policyd?-spf/) { postfix_policy_spf($p1); next; } # postfix/policy-spf if ($service_name =~ /^policyd-?weight/) { postfix_policydweight($p1); next; } # postfix/policydweight =cut # Handle policy service handlers before panic, fatal, warning, etc. # messages so that service-specific code gets first crack. # 5:25 foreach (@policy_services) { if ($service_name =~ $_->[0]) { #print "Calling policy service helper: $service_name:('$p1')\n"; &{$_->[1]}($p1); next LINE; } }; #=cut # ^warning: ... # ^fatal: ... # ^panic: ... # ^error: ... if ($p1 =~ /^warning: +(.*)$/) { postfix_warning($1); next; } if ($p1 =~ /^fatal: +(.*)$/) { postfix_fatal($1); next; } if ($p1 =~ /^panic: +(.*)$/) { postfix_panic($1); next; } if ($p1 =~ /^error: +(.*)$/) { postfix_error($1); next; } # Backwards compatibility mode if ($p1 =~ /compati/i) { backwards_compatible($p1); next; } # backwards-compatible default settings # output by all services that use table lookups - process before specific messages if ($p1 =~ /(?:lookup )?table (?:[^ ]+ )?has changed -- (?:restarting|exiting)$/) { #TD table hash:/var/mailman/data/virtual-mailman(0,lock|fold_fix) has changed -- restarting #TD table hash:/etc/postfix/helo_checks has changed -- restarting $Totals{'tablechanged'}++; next; } # postfix/postscreen and postfix/verify services if ($service_name eq 'postscreen' or $service_name eq 'verify') { postfix_postscreen($p1); next; } # postfix/postscreen, postfix/verify if ($service_name eq 'dnsblog') { postfix_dnsblog($p1); next; } # postfix/dnsblog if ($service_name =~ /^cleanup/) { postfix_cleanup($p1); next; } # postfix/cleanup* if ($service_name =~ /^bounce/) { postfix_bounce($p1); next; } # postfix/bounce* if ($service_name eq 'postfix-script') { postfix_script($p1); next; } # postfix/postfix-script if ($service_name eq 'postsuper') { postfix_postsuper($p1); next; } # postfix/postsuper # ignore tlsproxy for now if ($service_name eq 'tlsproxy') { next; } # postfix/tlsproxy my ($helo, $relay, $from, $origto, $to, $domain, $status, $type, $reason, $reason2, $filter, $site, $cmd, $qid, $rej_type, $reject_name, $host, $hostip, $dsn, $reply, $fmthost, $bytes); $rej_type = undef; # ^$re_QID: ... if ($p1 =~ s/^($re_QID): //o) { $qid = $1; next if ($p1 =~ /^host \S*\[\S*\] said: 4\d\d/); # deferrals, picked up in "status=deferred" if ($p1 =~ /^removed\s*$/ ) { # Note: See REMOVED elsewhere # 52CBDC2E0F: removed delete $SizeByQid{$qid} if (exists $SizeByQid{$qid}); $Totals{'removedfromqueue'}++; next; } # coerce into general warning if (($p1 =~ /^Cannot start TLS: handshake failure/) or ($p1 =~ /^non-E?SMTP response from/)) { postfix_warning($p1); next; } if ($p1 eq 'status=deferred (bounce failed)') { #TDqQ status=deferred (bounce failed) $Totals{'bouncefailed'}++; next; } # this test must preceed access checks below #TDsQ replace: header From: "Postmaster" : From: "Postmaster" if ($service_name eq 'smtp' and header_body_checks($p1)) { #print "main: header_body_checks\n"; next; } # Postfix access actions # REJECT optional text... # DISCARD optional text... # HOLD optional text... # WARN optional text... # FILTER transport:destination # REDIRECT user@domain # BCC user@domain (2.6 experimental branch) # The following actions are indistinguishable in the logs # 4NN text # 5NN text # DEFER_IF_REJECT optional text... # DEFER_IF_PERMIT optional text... # UCE restriction... # The following actions are not logged # PREPEND headername: headervalue # DUNNO # # Reject actions based on remote client information: # - one of host name, network address, envelope sender # or # - recipient address # Template of access controls. Rejects look like the first line, other access actions the second. # ftph is envelope from, envelope to, proto and helo. # QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph # QID: ACTION STAGE from host[hostip]: trigger: explanation; ftph # $re_QID: reject: RCPT|MAIL|CONNECT|HELO|DATA from ... # $re_QID: reject_warning: RCPT|MAIL|CONNECT|HELO|DATA from ... if ($p1 =~ /^(reject(?:_warning)?|discard|filter|hold|redirect|warn|bcc|replace): /) { my $action = $1; $p1 = substr($p1, length($action) + 2); #print "action: \"$action\", p1: \"$p1\"\n"; if ($p1 !~ /^(RCPT|MAIL|CONNECT|HELO|EHLO|DATA|VRFY|ETRN|END-OF-MESSAGE) from ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/) { inc_unmatched('unexpected access'); next; } my ($stage,$host,$hostip,$p1) = ($1,$2,$3,$4); #print "stage: \"$stage\", host: \"$host\", hostip: \"$hostip\", p1: \"$p1\"\n"; my ($efrom,$eto,$proto,$helo) = strip_ftph($p1); #print "efrom: \"$efrom\", eto: \"$eto\", proto: \"$proto\", helo: \"$helo\"\n"; #print "p1 now: \"$p1\"\n"; # QID: ACTION STAGE from host[hostip]: DSN trigger: explanation; ftph #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= #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> #TDsdN reject: VRFY from host[10.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=SMTP #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= proto=SMTP #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 : Recipient address rejected: User unknown in local recipient table; from=<> to= proto=SMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 550 : Recipient address rejected: User unknown in local recipient table; from=<> to= proto=SMTP helo= #TDsdN reject_warning: RCPT from host[10.0.0.1]: 550 : Recipient address rejected: User unknown in local recipient table; from=<> to= proto=SMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 550 5.1.1 : Recipient address rejected: User unknown in virtual address table; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.1 : Recipient address rejected: User unknown in virtual mailbox table; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 550 5.5.0 : Recipient address rejected: User unknown; from= to= proto=ESMTP helo=<[10.0.0.1]> #TDsdN reject: RCPT from host[10.0.0.1]: 450 : Recipient address rejected: Greylisted; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= #TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 : Recipient address rejected: Access denied; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.2 : Recipient address rejected: Domain not found; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 554 : Recipient address rejected: Please see http://www.openspf.org/why.html?sender=from%40example.net&ip=10.0.0.1&receiver=example.net; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 550 : Recipient address rejected: undeliverable address: host example.net[192.168.0.1] said: 550 : User unknown in virtual alias table (in reply to RCPT TO command); from= to= proto=SMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 554 : Recipient address rejected: Please see http://spf.pobox.com/why.html?sender=user%40example.com&ip=10.0.0.1&receiver=mail; from= to= proto=ESMTP helo=<10.0.0.1> #TDsdN reject: RCPT from host[10.0.0.1]: 554 : Relay access denied; from= to= proto=SMTP helo= #TDsdN reject_warning: HELO from host[10.0.0.1]: 554 : Relay access denied; proto=SMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=ESMTP helo= #TDsdN reject_warning: RCPT from host[10.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 550 : Sender address rejected: undeliverable address: host example.net[10.0.0.1] said: 550 : User unknown in virtual alias table (in reply to RCPT TO command); from= to= proto=SMTP helo= #TDsdN reject_warning: RCPT from host[10.0.0.1]: 554 : Client host rejected: Access denied; from= to= proto=SMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 554 : Client host rejected: Optional text; from= to= proto=SMTP helo= #TDsdN reject: CONNECT from host[10.0.0.1]: 503 5.5.0 : Client host rejected: Improper use of SMTP command pipelining; proto=SMTP #TDsdN reject_warning: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from unk[10.0.0.1]: 450 Client host rejected: cannot find your hostname, [10.0.0.1]; proto=ESMTP #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] #TDsdN reject: CONNECT from unk[unknown]: 421 4.7.1 Client host rejected: cannot find your reverse hostname, [unknown]; proto=SMTP #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= to= proto=ESMTP helo= #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= to= proto=ESMTP helo= #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= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 454 4.7.1 : Helo command rejected: Access denied; from= to= proto=SMTP helo= #TDsdN reject_warning: RCPT from host[10.0.0.1]: 454 4.7.1 : Helo command rejected: Access denied; from= to= proto=SMTP helo= #TDsdN reject: EHLO from host[10.0.0.1]: 504 5.5.2 : Helo command rejected: need fully-qualified hostname; proto=SMTP helo= #TDsdQ reject: DATA from host[10.0.0.1]: 550 5.5.3 : Data command rejected: Multi-recipient bounce; from=<> proto=ESMTP helo= #TDsdN reject: ETRN from host[10.0.0.1]: 554 5.7.1 : Etrn command rejected: Access denied; proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 452 Insufficient system storage; from= to= #TDsdN reject_warning: RCPT from host[10.0.0.1]: 451 4.3.5 Server configuration error; from= to= proto=ESMTP helo= #TDsdN reject: RCPT from host[10.0.0.1]: 450 Server configuration problem; from= to= proto=ESMTP helo= #TDsdN reject: MAIL from host[10.0.0.1]: 552 Message size exceeds fixed limit; proto=ESMTP helo= #TDsdN reject: RCPT from unk[10.0.0.1]: 554 5.7.1 : Unverified Client host rejected: Access denied; from= to= proto=SMTP helo= #TDsdN reject: MAIL from host[10.0.0.1]: 451 4.3.0 : Temporary lookup failure; from= proto=ESMTP helo= # reject, reject_warning if ($action =~ /^reject/) { my ($recip); if ($p1 !~ /^($re_DSN) (.*)$/o) { inc_unmatched('reject1'); next; } ($dsn,$p1) = ($1,$2); #print "dsn: $dsn, p1: \"$p1\"\n"; $fmthost = formathost($hostip,$host); # reject_warning override temp or perm reject types $rej_type = ($action eq 'reject_warning' ? 'warn' : get_reject_key($dsn)); #print "REJECT stage: '$rej_type'\n"; if ($Collecting{'byiprejects'} and substr($rej_type,0,1) eq '5') { $Counts{'byiprejects'}{$fmthost}++; } if ($stage eq 'VRFY') { if ($p1 =~ /^(?:<(\S*)>: )?(.*);$/) { my ($trigger,$reason) = ($1,$2); $Totals{$reject_name = "${rej_type}rejectverify" }++; next unless ($Collecting{$reject_name}); if ($reason =~ /^Service unavailable; Client host \[[^]]+\] (blocked using [^;]*);/) { $reason = join (' ', 'Client host blocked using', $1); $trigger = ''; } $Counts{$reject_name}{$reason}{$fmthost}{ucfirst($trigger)}++; } else { inc_unmatched('vrfyfrom'); } next; } # XXX there may be several semicolon-separated messages # Recipient address rejected: Unknown users and via check_recipient_access if ($p1 =~ /^<(.*)>: Recipient address rejected: ([^;]*);/) { ($recip,$reason) = ($1,$2); my ($localpart,$domainpart); # Unknown users; local mailbox, alias, virtual, relay user, unspecified if ($recip eq '') { ($localpart, $domainpart) = ('<>', '*unspecified'); } else { ($localpart, $domainpart) = split (/@/, lc $recip); ($localpart, $domainpart) = ($recip, '*unspecified') if ($domainpart eq ''); } if ($reason =~ s/^User unknown *//) { $Totals{$reject_name = "${rej_type}rejectunknownuser" }++; next unless ($Collecting{$reject_name}); my ($table) = ($reason =~ /^in ((?:\w+ )+table)/); $table = 'Address table unavailable' if ($table eq ''); # when show_user_unknown_table_name=no $Counts{$reject_name}{ucfirst($table)}{$domainpart}{$localpart}{$fmthost}++; } else { # check_recipient_access $Totals{$reject_name = "${rej_type}rejectrecip" }++; next unless ($Collecting{$reject_name}); if ($reason =~ m{^Please see http://[^/]+/why\.html}) { $reason = 'SPF reject'; } elsif ($reason =~ /^undeliverable address: host ([^[]+)\[([^]]+)\](?::\d+)? said:/) { $reason = 'undeliverable address: remote host rejected recipient'; } $Counts{$reject_name}{ucfirst($reason)}{$domainpart}{$localpart}{$fmthost}++; } } elsif ($p1 =~ /^<(.*?)>.* Relay access denied/) { $Totals{$reject_name = "${rej_type}rejectrelay" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$eto}++; } elsif ($p1 =~ /^<(.*)>: Sender address rejected: (.*);/) { $Totals{$reject_name = "${rej_type}rejectsender" }++; next unless ($Collecting{$reject_name}); ($from,$reason) = ($1,$2); if ($reason =~ /^undeliverable address: host ([^[]+)\[([^]]+)\](?::\d+)? said:/) { $reason = 'undeliverable address: remote host rejected sender'; } $Counts{$reject_name}{ucfirst($reason)}{$fmthost}{$from ne '' ? $from : '<>'}++; } elsif ($p1 =~ /^(?:<.*>: )?Unverified Client host rejected: /) { # check_reverse_client_hostname_access (postfix 2.6+) $Totals{$reject_name = "${rej_type}rejectunverifiedclient" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++; } elsif ($p1 =~ s/^(?:<.*>: )?Client host rejected: //) { # reject_unknown_client # client IP->name mapping fails # name->IP mapping fails # name->IP mapping =! client IP if ($p1 =~ /^cannot find your hostname/) { $Totals{$reject_name = "${rej_type}rejectunknownclient" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$helo}{$eto}{$efrom}++; } # reject_unknown_reverse_client_hostname (no PTR record for client's IP) elsif ($p1 =~ /^cannot find your reverse hostname/) { $Totals{$reject_name = "${rej_type}rejectunknownreverseclient" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$hostip}++ } else { $Totals{$reject_name = "${rej_type}rejectclient" }++; next unless ($Collecting{$reject_name}); $p1 =~ s/;$//; $Counts{$reject_name}{ucfirst($p1)}{$fmthost}{$eto}{$efrom}++; } } elsif ($p1 =~ /^Service (?:temporarily )?(?:unavailable|denied)[^;]*; (?:(?:Unverified )?Client host |Sender address |Helo command )?\[[^ ]*\] blocked using ([^;]+);/) { # Note: similar code below: search RejectRBL # postfix 2.1 #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= to= proto=ESMTP helo= # postfix 2.3+ #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= to= proto=SMTP helo= #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 ; from= to= proto=SMTP helo=<10.0.0.1> $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name}); ($site,$reason) = ($1 =~ /^(.+?)(?:$|(?:[.,] )(.*))/); $reason =~ s/^reason: // if ($reason); $Counts{$reject_name}{$site}{$fmthost}{$reason ? $reason : ''}++; } elsif ($p1 =~ /^<.*>: Helo command rejected: (.*);$/) { $Totals{$reject_name = "${rej_type}rejecthelo" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{ucfirst($1)}{$fmthost}{$helo}++; } elsif ($p1 =~ /^<.*>: Etrn command rejected: (.*);$/) { $Totals{$reject_name = "${rej_type}rejectetrn" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{ucfirst($1)}{$fmthost}{$helo}++; } elsif ($p1 =~ /^<.*>: Data command rejected: (.*);$/) { $Totals{$reject_name = "${rej_type}rejectdata" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$1}{$fmthost}{$helo}++; } elsif ($p1 =~ /^Insufficient system storage;/) { $Totals{'warninsufficientspace'}++; # force display in Warnings section also $Totals{$reject_name = "${rej_type}rejectinsufficientspace" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++; } elsif ($p1 =~ /^Server configuration (?:error|problem);/) { $Totals{'warnconfigerror'}++; # force display in Warnings section also $Totals{$reject_name = "${rej_type}rejectconfigerror" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++; } elsif ($p1 =~ /^Message size exceeds fixed limit;$/) { # Postfix responds with this message after a MAIL FROM:<...> SIZE=nnn command, where postfix consider's nnn excessive # Note: similar code below: search RejectSize # Note: reject_warning does not seem to occur $Totals{$reject_name = "${rej_type}rejectsize" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++; } elsif ($p1 =~ /^<(.*?)>: Temporary lookup failure;/) { $Totals{$reject_name = "${rej_type}rejectlookupfailure" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost}{$eto}{$efrom}++; # This would capture all other rejects, but I think it might be more useful to add # additional capture sections based on user reports of uncapture lines. # #} elsif ( ($reason) = ($p1 =~ /^([^;]+);/)) { # $Totals{$rej_type . 'rejectother'}++; # $Counts{$rej_type . 'rejectother'}{$reason}++; } else { inc_unmatched('rejectother'); } } # end of $re_QID: reject: # QID: ACTION STAGE from host[hostip]: trigger: reason; ftph # #TDsdN warn: RCPT from host[10.0.0.1]: TEST access WARN action; from= to= proto=ESMTP helo= #TDsdN warn: RCPT from host[10.0.0.1]: ; from= to= proto=ESMTP helo= #TDsdN discard: RCPT from host[10.0.0.1]: : Sender address TEST DISCARD action; from= to= proto=ESMTP helo= #TDsdN discard: RCPT from host[10.0.0.1]: : Client host TEST DISCARD action w/ip(client_checks); from= to= proto=ESMTP helo= #TDsdN discard: RCPT from host[10.0.0.1]: : Unverified Client host triggers DISCARD action; from= to= proto=ESMTP helo=<10.0.0.1> #TDsdN hold: RCPT from host[10.0.0.1]: : Recipient address triggers HOLD action; from= to= proto=SMTP helo=<10.0.0.1> #TDsdN hold: RCPT from host[10.0.0.1]: : Helo command optional text...; from= to= proto=ESMTP helo= #TDsdN hold: RCPT from host[10.0.0.1]: : Helo command triggers HOLD action; from= to= proto=ESMTP helo= #TDsdN hold: DATA from host[10.0.0.1]: : Helo command triggers HOLD action; from= to= proto=ESMTP helo= #TDsdN filter: RCPT from host[10.0.0.1]: <>: Sender address triggers FILTER filter:somefilter; from=<> to= proto=SMTP helo= #TDsdN filter: RCPT from host[10.0.0.1]: : Recipient address triggers FILTER smtp-amavis:[127.0.0.1]:10024; from= to= proto=SMTP helo= #TDsdN redirect: RCPT from host[10.0.0.1]: : Client host triggers REDIRECT root@localhost; from= to= proto=SMTP helo= #TDsdN redirect: RCPT from host[10.0.0.1]: : Recipient address triggers REDIRECT root@localhost; from= to= proto=ESMTP helo= # BCC action (postfix 2.6+) #TDsdN bcc: RCPT from host[10.0.0.1]: : Sender address triggers BCC root@localhost; from= to= proto=ESMTP helo= # $re_QID: discard, filter, hold, redirect, warn, bcc, replace ... else { my $trigger; ($trigger,$reason) = ($p1 =~ /^(?:<(\S*)>: )?(.*);$/ ); if ($trigger eq '') { $trigger = '*unavailable'; } else { $trigger =~ s/^<(.+)>$/$1/; } $reason = '*unavailable' if ($reason eq ''); $fmthost = formathost ($hostip,$host); #print "trigger: \"$trigger\", reason: \"$reason\"\n"; # reason -> subject text # subject -> "Helo command" : smtpd_helo_restrictions # subject -> "Client host" : smtpd_client_restrictions # subject -> "Unverified Client host" : smtpd_client_restrictions # subject -> "Client certificate" : smtpd_client_restrictions # subject -> "Sender address" : smtpd_sender_restrictions # subject -> "Recipient address" : smtpd_recipient_restrictions # subject -> "Data command" : smtpd_data_restrictions # subject -> "End-of-data" : smtpd_end_of_data_restrictions # subject -> "Etrn command" : smtpd_etrn_restrictions # text -> triggers action|triggers |optional text... my ($subject, $text) = ($reason =~ /^((?:Recipient|Sender) address|(?:Unverified )?Client host|Client certificate|(?:Helo|Etrn|Data) command|End-of-data) (.+)$/o); #printf "ACTION: '$action', SUBJECT: %-30s TEXT: \"$text\"\n", '"' . $subject . '"'; if ($action eq 'filter') { $Totals{'filtered'}++; next unless ($Collecting{'filtered'}); # See "Note: Counts" before changing $Counts below re: Filtered $text =~ s/triggers FILTER //; if ($subject eq 'Recipient address') { $Counts{'filtered'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; } elsif ($subject =~ /Client host$/) { $Counts{'filtered'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; } else { $Counts{'filtered'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; } } elsif ($action eq 'redirect') { $Totals{'redirected'}++; next unless ($Collecting{'redirected'}); $text =~ s/triggers REDIRECT //; # See "Note: Counts" before changing $Counts below re: Redirected if ($subject eq 'Recipient address') { $Counts{'redirected'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; } elsif ($subject =~ /Client host$/) { $Counts{'redirected'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; } else { $Counts{'redirected'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; } } # hold, discard, and warn allow "optional text" elsif ($action eq 'hold') { $Totals{'hold'}++; next unless ($Collecting{'hold'}); # See "Note: Counts" before changing $Counts below re: Hold $subject = $reason unless $text eq 'triggers HOLD action'; if ($subject eq 'Recipient address') { $Counts{'hold'}{$subject}{$trigger}{$efrom}{$fmthost}++; } elsif ($subject =~ /Client host$/) { $Counts{'hold'}{$subject}{$fmthost}{$eto}{$efrom}++; } else { $Counts{'hold'}{$subject}{$trigger}{$eto}{$fmthost}++; } } elsif ($action eq 'discard') { $Totals{'discarded'}++; next unless ($Collecting{'discarded'}); # See "Note: Counts" before changing $Counts below re: Discarded $subject = $reason unless $text eq 'triggers DISCARD action'; if ($subject eq 'Recipient address') { $Counts{'discarded'}{$subject}{$trigger}{$efrom}{$fmthost}++; } elsif ($subject =~ /Client host$/) { $Counts{'discarded'}{$subject}{$fmthost}{$eto}{$efrom}++; } else { $Counts{'discarded'}{$subject}{$trigger}{$eto}{$fmthost}++; } } elsif ($action eq 'warn') { $Totals{'warned'}++; next unless ($Collecting{'warned'}); $Counts{'warned'}{$reason}{$fmthost}{$eto}{''}++; # See "Note: Counts" before changing $Counts above... } elsif ($action eq 'bcc') { $Totals{'bcced'}++; next unless ($Collecting{'bcced'}); # See "Note: Counts" before changing $Counts below re: Filtered $text =~ s/triggers BCC //o; if ($subject eq 'Recipient address') { $Counts{'bcced'}{$text}{$subject}{$trigger}{$efrom}{$fmthost}++; } elsif ($subject =~ /Client host$/) { $Counts{'bcced'}{$text}{$subject}{$fmthost}{$eto}{$efrom}++; } else { $Counts{'bcced'}{$text}{$subject}{$trigger}{$eto}{$fmthost}++; } } else { die "Unexpected ACTION: '$action'"; } } } elsif ($p1 =~ s/^client=(([^ ]*)\[([^ ]*)\](?::(?:\d+|unknown))?)//) { my ($hip,$host,$hostip) = ($1,$2,$3); # Increment accepted when the client connection is made and smtpd has a QID. # Previously, accepted was being incorrectly incremented when the first qmgr # "from=xxx, size=nnn ..." line was seen. This is erroneous when the smtpd # client connection occurred outside the date range of the log being analyzed. $AcceptedByQid{$qid} = $hip; $Totals{'msgsaccepted'}++; #TDsdQ client=unknown[192.168.0.1] #TDsdQ client=unknown[192.168.0.1]:unknown #TDsdQ client=unknown[192.168.0.1]:10025 #TDsd client=example.com[192.168.0.1], helo=example.com #TDsdQ client=mail.example.com[2001:dead:beef::1] #TDsdQ client=localhost[127.0.0.1], sasl_sender=someone@example.com #TDsdQ client=example.com[192.168.0.1], sasl_method=PLAIN, sasl_username=anyone@sample.net #TDsdQ client=example.com[192.168.0.1], sasl_method=LOGIN, sasl_username=user@example.com, sasl_sender= #TDsdQ client=unknown[10.0.0.1], sasl_sender=user@examine.com next if ($p1 eq ''); my ($method,$user,$sender) = ($p1 =~ /^(?:, sasl_method=([^,]+))?(?:, sasl_username=([^,]+))?(?:, sasl_sender=?)?$/); # sasl_sender occurs when AUTH verb is present in MAIL FROM, typically used for relaying # the username (eg. sasl_username) of authenticated users. if ($sender or $method or $user) { $Totals{'saslauth'}++; next unless ($Collecting{'saslauth'}); $method ||= '*unknown method'; $user ||= '*unknown user'; $Counts{'saslauth'}{$user . ($sender ? " ($sender)" : '')}{$method}{formathost($hostip,$host)}++; } } # ^$re_QID: ... (not access(5) action) elsif ($p1 =~ /^from=<(.*?)>, size=(\d+), nrcpt=(\d+)/) { my ($efrom,$bytes,$nrcpt) = ($1,$2,$3); #TDsdQ from=, size=4051, nrcpt=1 (queue active) #TDsdQ(12) from=, size=25302, nrcpt=2 (queue active) #TDsdQ from=, size=5529, nrcpt=1 (queue active) #TDsdQ from=, size=5335, nrcpt=1 (queue active) # Distinguish bytes accepted vs. bytes delivered due to multiple recips # Increment bytes accepted on the first qmgr "from=..." line... next if (exists $SizeByQid{$qid}); $SizeByQid{$qid} = $bytes; # ...but only when the smtpd "client=..." line has been seen too. # This under-counts when the smtpd "client=..." connection log entry and the # qmgr "from=..." log entry span different periods (as fed to postfix-logwatch). next if (! exists $AcceptedByQid{$qid}); $Totals{'bytesaccepted'} += $bytes; $Counts{'envelopesenders'}{$efrom ne '' ? $efrom : '<>'}++ if ($Collecting{'envelopesenders'}); if ($Collecting{'envelopesenderdomains'}) { my ($localpart, $domain); if ($efrom eq '') { ($localpart, $domain) = ('<>', '*unknown'); } else { ($localpart, $domain) = split (/@/, lc $efrom); } $Counts{'envelopesenderdomains'}{$domain ne '' ? $domain : '*unknown'}{$localpart}++; } delete $AcceptedByQid{$qid}; # prevent incrementing BytesAccepted again } ### sent, forwarded, bounced, softbounce, deferred, (un)deliverable # # postfix >= 3.11 will log TLS feature information in delivery status logging by default (tls=) # modified $re_DDD detects the corresponding tls= part, now # elsif ($p1 =~ s/^to=<(.*?)>,(?: orig_to=<(.*?)>,)? relay=([^,]*).*, ($re_DDD), status=(\S+) //o) { ($relay,$status) = ($3,$5); my ($to,$origto,$localpart,$domainpart,$dsn,$p1) = process_delivery_attempt ($1,$2,$4,$p1); my ($tlsfeatures) = $4 =~ /tls=([^,]+)/; if ($tlsfeatures ne '') { $domainpart = $domainpart . " (tls=" . $tlsfeatures . ")"; } # postfix <= 3.10: # #TD 552B6C20E: to=, 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) #TD 552B6C20E: to=, 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) #TD DD925BBE2: to=, orig_to=, relay=mail.example.net[2001:dead:beef::1], delay=2, status=sent (250 Ok: queued as 5221227246) # # postfix >= 3.11: # #TD4d9bVw113Wz1SwX: to=, relay=mail.example.org[10.0.01]:25, delay=3.2, delays=0.09/0.05/1.8/1.2, tls=dane, dsn=2.0.0, status=sent (250 OK id=1vLFwP-00000008xhU-3ZkI) #TD4d9bVw113Wz1SwX: to=, relay=mail.example.org[10.0.01]:25, delay=3.2, delays=0.09/0.05/1.8/1.2, tls=may, dsn=2.0.0, status=sent (250 OK id=1vLFwP-00000008xhU-3ZkI) ### sent if ($status eq 'sent') { if ($p1 =~ /forwarded as /) { $Totals{'bytesforwarded'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid}); $Totals{'forwarded'}++; next unless ($Collecting{'forwarded'}); $Counts{'forwarded'}{$domainpart}{$localpart}{$origto}++; } else { if ($service_name eq 'lmtp') { $Totals{'bytessentlmtp'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid}); $Totals{'sentlmtp'}++; next unless ($Collecting{'sentlmtp'}); $Counts{'sentlmtp'}{$domainpart}{$localpart}{$origto}++; } elsif ($service_name eq 'smtp') { $Totals{'bytessentsmtp'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid}); $Totals{'sent'}++; next unless ($Collecting{'sent'}); $Counts{'sent'}{$domainpart}{$localpart}{$origto}++; } # virtual, command, ... else { $Totals{'bytesdelivered'} += $SizeByQid{$qid} if (exists $SizeByQid{$qid}); $Totals{'delivered'}++; next unless ($Collecting{'delivered'}); $Counts{'delivered'}{$domainpart}{$localpart}{$origto}++; } } } elsif ($status eq 'deferred') { #TDsQ to=, 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) #TDsQ to=, relay=none, delay=141602, status=deferred (connect to mx1.example.com[10.0.0.1]: Connection refused) #TDsQ to=, relay=none, delay=141602, status=deferred (delivery temporarily suspended: connect to example.com[192.168.0.1]: Connection refused) #TDsQ to=, 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) #TDsQ to=, 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) #TDsQ to=, 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) #TDsQ to=, 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) #TDsQ to=, orig_to=, relay=none, delay=238024, status=deferred (delivery temporarily suspended: transport is unavailable) # XXX postfix reports dsn=5.0.0, host's reply may contain its own dsn's such as 511 and #5.1.1 # XXX should these be used instead? #TDsQ to=, 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 : Recipient address rejected: Greylisted (in reply to RCPT TO command)) #TDsQ to=, 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 : User unknown in local recipient table (in reply to RCPT TO command)) #TDsQ to=, 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 : Recipient address rejected: undeliverable address: User unknown in virtual alias table (in reply to RCPT TO command)) ($reply,$fmthost) = cleanhostreply($p1,$relay,$to,$domainpart); $Totals{'deferred'}++ if ($DeferredByQid{$qid}++ == 0); $Totals{'deferrals'}++; next unless ($Collecting{'deferrals'}); $Counts{'deferrals'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++; } ### bounced elsif ($status eq 'bounced' or $status eq 'SOFTBOUNCE') { # local agent #TDlQ to=, relay=local, delay=2.5, delays=2.1/0.22/0/0.21, dsn=5.1.1, status=bounced (unknown user: "friend") # smtp agent #TDsQ to=, orig_to=, 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)) #TDsQ to=, 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)) #TDsQ to=, orig_to=, 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)) #TDsQ to=, 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) #TDsQ to=<>, relay=none, delay=1, status=bounced (mail for sample.net loops back to myself) #TDsQ to=, relay=none, delay=0, status=bounced (Host or domain name not found. Name service error for name=unknown.com type=A: Host not found) # XXX verify these... #TD EB0B8770: to=, orig_to=, relay=none, delay=1, status=bounced (User unknown in virtual alias table) #TD EB0B8770: to=, orig_to=, relay=sample.net[192.168.0.1], delay=1.1, status=bounced (User unknown in relay recipient table) #TD D8962E54: to=, 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") #TD F031C832: to=, orig_to=, relay=local, delay=0.17, delays=0.13/0.01/0/0.03, dsn=5.1.1, status=bounced (unknown user: "to") #TD C76431E2: to=, relay=local, delay=2, status=SOFTBOUNCE (host sample.net[192.168.0.1] said: 450 : User unknown in local recipient table (in reply to RCPT TO command)) #TD 04B0702E: to=, 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)) #TD 9DAC8B2D: to=, 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)) #TD 79CB702D: to=, 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 , Recipient unknown (in reply to RCPT TO command)) #TD 88B7A079: to=, 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)) #TD 47B7B074: to=, 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 User unknown; rejecting (in reply to RCPT TO command)) #TDppQ to=, 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") # print "bounce message from " . $to . " msg : " . $relay . "\n"; # See same code elsewhere "Note: Bounce" ### local bounce # XXX local v. remote bounce seems iffy, relative if ($relay =~ /^(?:none|local|virtual|127\.0\.0\.1|maildrop|avcheck)/) { $Totals{'bouncelocal'}++; next unless ($Collecting{'bouncelocal'}); $Counts{'bouncelocal'}{get_dsn_msg($dsn)}{$domainpart}{ucfirst($p1)}{$localpart}++; } else { $Totals{'bounceremote'}++; next unless ($Collecting{'bounceremote'}); ($reply,$fmthost) = cleanhostreply($p1,$relay,$to,$domainpart); $Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++; } } elsif ($status =~ 'undeliverable') { #TDsQ to=, 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) #TDsQ to=, 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: User unknown (in reply to RCPT TO command)) #TDvQ to=, relay=virtual, delay=0.14, delays=0.06/0/0/0.08, dsn=5.1.1, status=undeliverable (unknown user: "u@example.com") #TDlQ to=, relay=local, delay=0.02, delays=0.01/0/0/0, dsn=5.1.1, status=undeliverable-but-not-cached (unknown user: "to") $Totals{'undeliverable'}++; next unless ($Collecting{'undeliverable'}); if ($p1 =~ /^unknown user: ".+?"$/) { $Counts{'undeliverable'}{get_dsn_msg($dsn)}{'Unknown user'}{$domainpart}{$localpart}{$origto ? $origto : ''}++; } else { my ($reply,$fmthost) = cleanhostreply($p1,'',$to ne '' ? $to : '<>',$domainpart); $Counts{'undeliverable'}{get_dsn_msg($dsn)}{$reply}{$domainpart}{$localpart}{$fmthost}++; } } elsif ($status eq 'deliverable') { # address verification, sendmail -bv deliverable reports #TDvQ to=, relay=virtual, delay=0.09, delays=0.03/0/0/0.06, dsn=2.0.0, status=deliverable (delivers to maildir) $Totals{'deliverable'}++; next unless ($Collecting{'deliverable'}); my $dsn = ($p1 =~ s/^($re_DSN) // ? $1 : '*unavailable'); $Counts{'deliverable'}{$dsn}{$p1}{$origto ? "$to ($origto)" : $to}++; } else { # keep this as the last condition in this else clause inc_unmatched('unknownstatus'); } } # end of sent, forwarded, bounced, softbounce, deferred, (un)deliverable # pickup elsif ($p1 =~ /^(uid=\S* from=<.*?>)/) { #TDpQ2 uid=0 from= $AcceptedByQid{$qid} = $1; $Totals{'msgsaccepted'}++; } elsif ($p1 =~ /^from=<(.*?)>, status=expired, returned to sender$/) { #TDqQ from=, status=expired, returned to sender $Totals{'returnedtosender'}++; next unless ($Collecting{'returnedtosender'}); $Counts{'returnedtosender'}{$1 ne '' ? $1 : '<>'}++; } elsif ($p1 =~ s/^host ([^[]+)\[([^]]+)\](?::\d+)? refused to talk to me://) { #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 #TDsQ host mail.example.com[10.0.0.1] refused to talk to me: 501 Connection from 192.168.2.1 (XY) rejected # Note: See ConnectToFailure above $Totals{'connecttofailure'}++; next unless ($Collecting{'connecttofailure'}); $Counts{'connecttofailure'}{'Refused to talk to me'}{formathost($2,$1)}{$p1}++; } elsif ($p1 =~ /^lost connection with ([^[]*)\[([^]]+)\](?::\d+)? (while .*)$/) { # outbound smtp #TDsQ lost connection with sample.net[10.0.0.1] while sending MAIL FROM #TDsQ lost connection with sample.net[10.0.0.2] while receiving the initial server greeting $Totals{'connectionlostoutbound'}++; next unless ($Collecting{'connectionlostoutbound'}); $Counts{'connectionlostoutbound'}{ucfirst($3)}{formathost($2,$1)}++; } elsif ($p1 =~ /^conversation with ([^[]*)\[([^]]+)\](?::\d+)? timed out (while .*)$/) { #TDsQ conversation with sample.net[10.0.0.1] timed out while receiving the initial SMTP greeting # Note: see TimeoutInbound below $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'}); $Counts{'timeoutinbound'}{ucfirst($3)}{formathost($2,$1)}{''}++; } elsif ($p1 =~ /^enabling PIX (\.) workaround for ([^[]+)\[([^]]+)\](?::\d+)?/ or $p1 =~ /^enabling PIX workarounds: (.*) for ([^[]+)\[([^]]+)\](?::\d+)?/) { #TDsQ enabling PIX . workaround for example.com[192.168.0.1] #TDsQ enabling PIX . workaround for mail.sample.net[10.0.0.1]:25 #TDsQ enabling PIX workarounds: disable_esmtp delay_dotcrlf for spam.example.org[10.0.0.1]:25 $Totals{'pixworkaround'}++; next unless ($Collecting{'pixworkaround'}); $Counts{'pixworkaround'}{$1}{formathost($3,$2)}++; } # milter-reject, milter-hold, milter-discard elsif ($p1 =~ s/^milter-//) { milter_common($p1); } elsif ($p1 =~ s/^SASL (\[CACHED\] )?authentication failed; //) { #TDsQ SASL authentication failed; cannot authenticate to server smtp.example.com[10.0.0.1]: no mechanism available #TDsQ SASL authentication failed; server example.com[10.0.0.1] said: 535 Error: authentication failed #TDsQ SASL [CACHED] authentication failed; server example.com[10.0.0.1] said: 535 Error: authentication failed # see saslauthfail elsewhere $Totals{'saslauthfail'}++; next unless ($Collecting{'saslauthfail'}); my $cached = $1; if ($p1 =~ /^(authentication protocol loop with server): ([^[]+)\[([^]]+)\](?::\d+)?$/) { ($reason,$host,$hostip,$reason2) = ($1,$2,$3,''); } elsif ($p1 =~ /^(cannot authenticate to server) ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/) { ($reason,$host,$hostip,$reason2) = ($1,$2,$3,$4); } elsif ($p1 =~ /^server ([^[]+)\[([^]]+)\](?::\d+)? said: (.+)$/) { ($reason,$host,$hostip,$reason2) = ('server ... said',$1,$2,$3); } else { inc_unmatched('saslauthfail'); next; } $reason .= ': ' . $reason2 if $reason2; $Counts{'saslauthfail'}{$cached . $reason}{formathost($hostip,$host)}++; } elsif (($host,$hostip) = ($p1 =~ /abort: TLS from ([^[]+)\[([^]]+)\]: No client certificate presented/)) { $Totals{'tlsservernocert'}++; next unless ($Collecting{'tlsservernocert'}); $Counts{'tlsservernocert'}{formathost($hostip,$host)}++; } elsif (($host,$hostip) = ($p1 =~ /abort: TLS from ([^[]+)\[([^]]+)\]: Client certificate not trusted/)) { $Totals{'tlsclientcertnottrusted'}++; next unless ($Collecting{'tlsclientcertnottrusted'}); $Counts{'tlsclientcertnottrusted'}{formathost($hostip,$host)}++; } # postfix >= 3.10: the following has to be covered under the $re_QID section # # < 3.10: # TDsd timeout after AUTH from unknown[1.2.3.4] # # >= 3.10: # TDsd NOQUEUE: timeout after AUTH from unknown[1.2.3.4] # # as NOQUEUE is matched as part of $re_QID the following events have to be processed here as well (just a copy): # # 3.10 release notes: # The SMTP server now logs the queue ID (or "NOQUEUE") when a connection # ends abnormally (timeout, lost connection, or too many errors), # and the cleanup server now logs "queueid: canceled" when a message # transaction is started but not completed. These changes simplify # logfile analysis. # elsif ($p1 =~ /^(timeout|lost connection) (after [^ ]+)(?: \((?:approximately )?(\d+) bytes\))? from ([^[]*)\[([^]]+)\](?::\d+)?$/) { my ($lort,$reason,$bytes,$host,$hostip) = ($1,$2,$3,$4,$5); if ($lort eq 'timeout') { # see also TimeoutInbound in $re_QID section #TDsd timeout after RSET from example.com[192.168.0.1] #TDsd timeout after DATA (6253 bytes) from example.com[10.0.0.1] $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'}); $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++; } else { #TDsd lost connection after CONNECT from mail.example.com[192.168.0.1] # postfix 2.5:20071003 #TDsd lost connection after DATA (494133 bytes) from localhost[127.0.0.1] # postfix 2.6:20080621 #TDsd lost connection after DATA (approximately 0 bytes) from example.com[10.0.0.1] $Totals{'connectionlostinbound'}++; next unless ($Collecting{'connectionlostinbound'}); $Counts{'connectionlostinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++; } } elsif ($p1 =~ /^(too many errors) (after [^ ]*)(?: \((?:approximately )?\d+ bytes\))? from ([^[]*)\[([^]]+)\](?::\d+)?$/) { my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4); #TDsd too many errors after AUTH from sample.net[10.0.0.1] #TDsd too many errors after DATA (0 bytes) from 1-0-0-10.example.com[10.0.0.1] $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'}); $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++; } # end of postfix >= 3.10 and $re_QID section else { # keep this as the last condition in this else clause inc_unmatched('unknownqid') if ! in_ignore_list ($p1); } } # end of $re_QID section # postfix < 3.10: # elsif ($p1 =~ /^(timeout|lost connection) (after [^ ]+)(?: \((?:approximately )?(\d+) bytes\))? from ([^[]*)\[([^]]+)\](?::\d+)?$/) { my ($lort,$reason,$bytes,$host,$hostip) = ($1,$2,$3,$4,$5); if ($lort eq 'timeout') { # see also TimeoutInbound in $re_QID section #TDsd timeout after RSET from example.com[192.168.0.1] #TDsd timeout after DATA (6253 bytes) from example.com[10.0.0.1] $Totals{'timeoutinbound'}++; next unless ($Collecting{'timeoutinbound'}); $Counts{'timeoutinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++; } else { #TDsd lost connection after CONNECT from mail.example.com[192.168.0.1] # postfix 2.5:20071003 #TDsd lost connection after DATA (494133 bytes) from localhost[127.0.0.1] # postfix 2.6:20080621 #TDsd lost connection after DATA (approximately 0 bytes) from example.com[10.0.0.1] $Totals{'connectionlostinbound'}++; next unless ($Collecting{'connectionlostinbound'}); $Counts{'connectionlostinbound'}{ucfirst($reason)}{formathost($hostip,$host)}{commify($bytes)}++; } } elsif ($p1 =~ /^(reject(?:_warning)?): RCPT from ([^[]+)\[([^]]+)\](?::\d+)?: ($re_DSN) Service (?:temporarily )?(?:unavailable|denied)[^;]*; (?:(?:Unverified )?Client host |Sender address |Helo command )?\[[^ ]*\] blocked using ([^;]+);/o) { my ($rej_type,$host,$hostip,$dsn,) = ($1,$2,$3,$4); ($site,$reason) = ($5 =~ /^(.+?)(?:$|(?:[.,] )(.*))/); $reason =~ s/^reason: // if ($reason); $rej_type = ($rej_type =~ /_warning/ ? 'warn' : get_reject_key($dsn)); #print "REJECT RBL NOQ: '$rej_type'\n"; # Note: similar code above: search RejectRBL # This section required: postfix didn't always log QID (eg. postfix 1.1) # Also, "reason:" was probably always present in this case, but I'm not certain # postfix 1.1 #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= to= #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= to= #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= to= #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= to= if ($Collecting{'byiprejects'} and substr($rej_type,0,1) eq '5') { $fmthost = formathost($hostip,$host); $Counts{'byiprejects'}{$fmthost}++; } $Totals{$reject_name = "${rej_type}rejectrbl" }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$site}{$fmthost ? $fmthost : formathost($hostip,$host)}{$reason ? $reason : ''}++; } # proxy-reject, proxy-accept elsif ($p1 =~ s/^proxy-(reject|accept): ([^:]+): //) { # 2.7 #TDsdN proxy-accept: END-OF-MESSAGE: 250 2.0.0 Ok: queued as 9BE3547AFE; from= to= proto=ESMTP helo= #TDsdN proxy-reject: END-OF-MESSAGE: 554 5.7.0 Reject, id=11912-03 - INFECTED: Eicar-Test-Signature; from= to= proto=ESMTP helo= #TDsdN proxy-reject: END-OF-MESSAGE: ; from= to= proto=SMTP helo= next if $1 eq 'accept'; #ignore accepts my ($stage) = ($2); my ($efrom,$eto,$proto,$helo) = strip_ftph($p1); #print "efrom: '$efrom', eto: '$eto', proto: '$proto', helo: '$helo'\n"; #print "stage: '$stage', reply: '$p1'\n"; my ($dsn,$reject_name); ($dsn,$reply) = ($1,$2) if $p1 =~ /^($re_DSN) (.*)$/o; #print " dsn: '$dsn', reply: '$reply', key: ", get_reject_key($dsn), "\n"; # DSN may not be present. Can occur, for example, when queue file size limit is reached, # which is logged as a Warning. Ignore these, since they can't be add to any # reject section (no SMTP reply code). if (! defined $dsn) { next; } $Totals{$reject_name = get_reject_key($dsn) . 'rejectproxy' }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$stage}{$reply}{$eto}++; } ### smtpd_tls_loglevel >= 1 # Server TLS messages elsif (($status,$host,$hostip,$type) = ($p1 =~ /^(?:(Anonymous|Trusted|Untrusted) )?TLS connection established from ([^[]+)\[([^]]+)\](?::\d+)?(?: to [^:]+)?: (.*)$/)) { #TDsd TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits) # Postfix 2.5+: status: Untrusted or Trusted #TDsd Untrusted TLS connection established from example.com[192.168.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits) #TDsd Anonymous TLS connection established from localhost[127.0.0.1]: TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits) $Totals{'tlsserverconnect'}++; next unless ($Collecting{'tlsserverconnect'}); $Counts{'tlsserverconnect'}{$status ? "$status: $type" : $type}{formathost($hostip,$host)}++; } # Client TLS messages elsif ( ($status,$host,$type) = ($p1 =~ /^(?:(Verified|Trusted|Untrusted|Anonymous) )?TLS connection established to ([^ ]*): (.*)$/o)) { #TD TLS connection established to example.com: TLSv1 with cipher AES256-SHA (256/256 bits) # Postfix 2.5+: peer verification status: Untrusted, Trusted or Verified when # server's trust chain is valid and peername is matched #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) $Totals{'tlsclientconnect'}++; next unless ($Collecting{'tlsclientconnect'}); $Counts{'tlsclientconnect'}{$status ? "$status: $type" : $type}{$host}++; } # smtp_tls_note_starttls_offer=yes elsif ($p1 =~ /^Host offered STARTTLS: \[(.*)\]$/o) { #TD Host offered STARTTLS: [mail.example.com] $Totals{'tlsoffered'}++; next unless ($Collecting{'tlsoffered'}); $Counts{'tlsoffered'}{$1}++; } ### smtpd_tls_loglevel >= 1 elsif ($p1 =~ /^Unverified: (.*)/o) { #TD Unverified: subject_CN=(www|smtp|mailhost).(example.com|sample.net), issuer=someuser $Totals{'tlsunverified'}++; next unless ($Collecting{'tlsunverified'}); $Counts{'tlsunverified'}{$1}++; } # Note: no QID elsif (($host,$hostip,$dsn,$from,$to) = ($p1 =~ /^reject: RCPT from ([^[]+)\[([^]]+)\](?::\d+)?: ([45]52) Message size exceeds fixed limit; from=<(.*?)> to=<(.*?)>/)) { #TD reject: RCPT from size.example.com[192.168.0.1]: 452 Message size exceeds fixed limit; from= to= #TD reject: RCPT from size.example.com[192.168.0.1]: 552 Message size exceeds fixed limit; from= to= proto=ESMTP helo= # Note: similar code above: search RejectSize # Note: reject_warning does not seem to occur if ($Collecting{'byiprejects'} and substr($dsn,0,1) eq '5') { $fmthost = formathost($hostip,$host); $Counts{'byiprejects'}{$fmthost}++; } $Totals{$reject_name = get_reject_key($dsn) . 'rejectsize' }++; next unless ($Collecting{$reject_name}); $Counts{$reject_name}{$fmthost ? $fmthost : formathost($hostip,$host)}{$to}{$from ne '' ? $from : '<>'}++; } elsif ($p1 =~ /looking for plugins in (.*)$/) { #TD looking for plugins in '/usr/lib/sasl2', failed to open directory, error: No such file or directory $Totals{'warnconfigerror'}++; next unless ($Collecting{'warnconfigerror'}); $Counts{'warnconfigerror'}{$1}++; } # SMTP/ESMTP protocol violations elsif ($p1 =~ /^(improper command pipelining) (after \S+) from ([^[]*)\[([^]]+)\](?::\d+)?/) { # ProtocolViolation #TDsd postfix/smtpd[24928]: improper command pipelining after RCPT from unknown[192.168.0.1] my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4); $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'}); $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++; } elsif ($p1 =~ /^(too many errors) (after [^ ]*)(?: \((?:approximately )?\d+ bytes\))? from ([^[]*)\[([^]]+)\](?::\d+)?$/) { my ($error,$stage,$host,$hostip) = ($1,$2,$3,$4); #TDsd too many errors after AUTH from sample.net[10.0.0.1] #TDsd too many errors after DATA (0 bytes) from 1-0-0-10.example.com[10.0.0.1] $Totals{'smtpprotocolviolation'}++; next unless ($Collecting{'smtpprotocolviolation'}); $Counts{'smtpprotocolviolation'}{ucfirst($error)}{ucfirst($stage)}{formathost($hostip,$host)}++; } # coerce these into general warnings elsif ( $p1 =~ /^cannot load Certificate Authority data/ or $p1 =~ /^SSL_connect error to /) { #TDsQ Cannot start TLS: handshake failure #TDsd cannot load Certificate Authority data #TDs SSL_connect error to mail.example.com: 0 postfix_warning($p1); } else { # add to the unmatched list if not on the ignore_list inc_unmatched('final') if ! in_ignore_list ($p1); } } ######################################## # Final tabulations, and report printing for my $code (@RejectKeys) { for my $type (@RejectClasses) { $Totals{'totalrejects' . $code} += $Totals{$code . $type}; } if ($code =~ /^5/o) { $Totals{'totalrejects'} += $Totals{'totalrejects' . $code}; } } # XXX this was naive - the goal was to avoid recounting messages # released from quarantine, but externally introduced messages may # contain resent-message-id; trying to track only internally resent # messages does not seem useful. # make some corrections now, due to double counting #$Totals{'msgsaccepted'} -= $Totals{'resent'} if ($Totals{'msgsaccepted'} >= $Totals{'resent'}); $Totals{'totalacceptplusreject'} = $Totals{'msgsaccepted'} + $Totals{'totalrejects'}; # Print the Summary report if any key has non-zero data. # Note: must explicitly check for any non-zero data, # as Totals always has some keys extant. # if ($Opts{'summary'}) { for (keys %Totals) { if ($Totals{$_}) { print_summary_report (@Sections); last; } } } # Print the Detail report, if detail is sufficiently high # if ($Opts{'detail'} >= 5) { #print STDERR "Counts memory usage: ", commify(Devel::Size::total_size(\%Counts)), "\n"; #print STDERR "Delays memory usage: ", commify(Devel::Size::total_size(\%Delays)), "\n"; print_detail_report(@Sections); if ($Opts{'delays'}) { my @table; for (sort keys %Delays) { # anon array ref: label, array ref of $Delay{key} push @table, [ substr($_,3), $Delays{$_} ]; } if (@table) { print_percentiles_report2(\@table, "Delivery Delays Percentiles", $Opts{'delays_percentiles'}); } } print_postgrey_reports(); } # Print the mailqueue if requested my $pathto_postqueue = $ENV{'pathto_postqueue'} || '/usr/sbin/postqueue'; if ($Opts{'mailqueue'} and -x $pathto_postqueue) { open(QUEUE,"$pathto_postqueue -p |"); while () { # Do not display empty queue if detail < 5 next if /Mail queue is empty/ and $Opts{'detail'} < 5; print; } close(QUEUE); print "\n"; } # debug: show which ignore_list items are hit most #my %IGNORED; #for (sort { $IGNORED{$b} <=> $IGNORED{$a} } keys %IGNORED) { # printf "%10d: KEY: %s\n", $IGNORED{$_}, $_; #} # Finally, print any unmatched lines # print_unmatched_report(); # # End of main # ################################################## # Create the list of REs against which log lines are matched. # Lines that match any of the patterns in this list are ignored. # # Note: This table is created at runtime, due to a Perl bug which # I reported as perl bug #56202: # # http://rt.perl.org/rt3/Public/Bug/Display.html?id=56202 # sub create_ignore_list() { # top 3 hitters up front push @ignore_list, qr/^statistics:/; push @ignore_list, qr/^setting up TLS connection (?:from|to)/; push @ignore_list, qr/^Verified: /; push @ignore_list, qr/^skipped, still being delivered/; # SSL info at/above mail.info level push @ignore_list, qr/^read from [a-fA-F\d]{8}/; push @ignore_list, qr/^write to [a-fA-F\d]{8}/; push @ignore_list, qr/^[a-f\d]{4} [a-f\d]{2}/; push @ignore_list, qr/^[a-f\d]{4} - ]+ /; push @ignore_list, qr/^premature end-of-input (?:on|from) .* socket while reading input attribute name$/; push @ignore_list, qr/^certificate peer name verification failed/; push @ignore_list, qr/^Peer certi?ficate could not be verified$/; # missing i was a postfix typo push @ignore_list, qr/^Peer cert verify depth=/; push @ignore_list, qr/^Peer verification:/; push @ignore_list, qr/^Server certificate could not be verified/; push @ignore_list, qr/^cannot load .SA certificate and key data/; push @ignore_list, qr/^tlsmgr_cache_run_event/; push @ignore_list, qr/^SSL_accept/; push @ignore_list, qr/^SSL_connect:/; push @ignore_list, qr/^connection (?:closed|established)/; push @ignore_list, qr/^delete smtpd session/; push @ignore_list, qr/^put smtpd session/; push @ignore_list, qr/^save session/; push @ignore_list, qr/^Reusing old/; push @ignore_list, qr/^looking up session/; push @ignore_list, qr/^lookup smtpd session/; push @ignore_list, qr/^lookup \S+ type/; push @ignore_list, qr/^xsasl_(?:cyrus|dovecot)_/; push @ignore_list, qr/^watchdog_/; push @ignore_list, qr/^read smtpd TLS/; push @ignore_list, qr/^open smtpd TLS/; push @ignore_list, qr/^write smtpd TLS/; push @ignore_list, qr/^read smtp TLS cache entry/; push @ignore_list, qr/^starting TLS engine$/; push @ignore_list, qr/^initializing the server-side TLS/; push @ignore_list, qr/^global TLS level: /; push @ignore_list, qr/^auto_clnt_/; push @ignore_list, qr/^generic_checks:/; push @ignore_list, qr/^inet_addr_/; push @ignore_list, qr/^mac_parse:/; push @ignore_list, qr/^cert has expired/; push @ignore_list, qr/^daemon started/; push @ignore_list, qr/^master_notify:/; push @ignore_list, qr/^rewrite_clnt:/; push @ignore_list, qr/^rewrite stream/; push @ignore_list, qr/^dict_/; push @ignore_list, qr/^send attr /; push @ignore_list, qr/^match_/; push @ignore_list, qr/^input attribute /; push @ignore_list, qr/^Run-time/; push @ignore_list, qr/^Compiled against/; push @ignore_list, qr/^private\//; push @ignore_list, qr/^reject_unknown_/; # don't combine or shorten these reject_ patterns push @ignore_list, qr/^reject_unauth_/; push @ignore_list, qr/^reject_non_/; push @ignore_list, qr/^permit_/; push @ignore_list, qr/^idle timeout/; push @ignore_list, qr/^get_dns_/; push @ignore_list, qr/^dns_/; push @ignore_list, qr/^chroot /; push @ignore_list, qr/^process generation/; push @ignore_list, qr/^fsspace:/; push @ignore_list, qr/^master disconnect/; push @ignore_list, qr/^resolve_clnt/; push @ignore_list, qr/^ctable_/; push @ignore_list, qr/^extract_addr/; push @ignore_list, qr/^mynetworks:/; push @ignore_list, qr/^name_mask:/; #TDm reload -- version 2.6-20080814, configuration /etc/postfix #TDm reload configuration /etc/postfix push @ignore_list, qr/^reload (?:-- version \S+, )?configuration/; push @ignore_list, qr/^terminating on signal 15$/; push @ignore_list, qr/^verify error:num=/; push @ignore_list, qr/^verify return:/; push @ignore_list, qr/^nss_ldap: /; push @ignore_list, qr/^discarding EHLO keywords: /; push @ignore_list, qr/^sql auxprop plugin/; push @ignore_list, qr/^sql plugin/; push @ignore_list, qr/^sql_select/; push @ignore_list, qr/^auxpropfunc error/; push @ignore_list, qr/^commit transaction/; push @ignore_list, qr/^begin transaction/; push @ignore_list, qr/^maps_find: /; push @ignore_list, qr/^check_access: /; push @ignore_list, qr/^check_domain_access: /; push @ignore_list, qr/^check_mail_access: /; push @ignore_list, qr/^check_table_result: /; push @ignore_list, qr/^mail_addr_find: /; push @ignore_list, qr/^mail_addr_map: /; push @ignore_list, qr/^mail_flow_put: /; push @ignore_list, qr/^smtp_addr_one: /; push @ignore_list, qr/^smtp_connect_addr: /; push @ignore_list, qr/^smtp_connect_unix: trying: /; push @ignore_list, qr/^smtp_find_self: /; push @ignore_list, qr/^smtp_get: /; push @ignore_list, qr/^smtp_fputs: /; push @ignore_list, qr/^smtp_parse_destination: /; push @ignore_list, qr/^smtp_sasl_passwd_lookup: /; push @ignore_list, qr/^smtpd_check_/; push @ignore_list, qr/^smtpd_chat_notify: /; push @ignore_list, qr/^been_here: /; push @ignore_list, qr/^set_eugid: /; push @ignore_list, qr/^deliver_/; push @ignore_list, qr/^flush_send_file: queue_id/; push @ignore_list, qr/^milter_macro_lookup/; push @ignore_list, qr/^milter8/; push @ignore_list, qr/^skipping non-protocol event/; push @ignore_list, qr/^reply: /; push @ignore_list, qr/^event: /; push @ignore_list, qr/^trying... /; push @ignore_list, qr/ all milters$/; push @ignore_list, qr/^vstream_/; push @ignore_list, qr/^server features/; push @ignore_list, qr/^skipping event/; push @ignore_list, qr/^Using /; push @ignore_list, qr/^rec_(?:put|get): /; push @ignore_list, qr/^subject=/; push @ignore_list, qr/^issuer=/; push @ignore_list, qr/^pref /; # yes, multiple spaces push @ignore_list, qr/^request: \d/; push @ignore_list, qr/^done incoming queue scan$/; push @ignore_list, qr/^qmgr_/; push @ignore_list, qr/^trigger_server_accept_fifo: /; push @ignore_list, qr/^proxymap stream/; push @ignore_list, qr/^(?:start|end) sorted recipient list$/; push @ignore_list, qr/^connecting to \S+ port /; push @ignore_list, qr/^Write \d+ chars/; push @ignore_list, qr/^Read \d+ chars/; push @ignore_list, qr/^(?:lookup|delete) smtp session/; push @ignore_list, qr/^delete smtp session/; push @ignore_list, qr/^(?:reloaded|remove|looking for) session .* cache$/; push @ignore_list, qr/^(?:begin|end) \S+ address list$/; push @ignore_list, qr/^mapping DSN status/; push @ignore_list, qr/^record [A-Z]/; push @ignore_list, qr/^dir_/; push @ignore_list, qr/^transport_event/; push @ignore_list, qr/^read [A-Z](?: |$)/; push @ignore_list, qr/^relay: /; push @ignore_list, qr/^why: /; push @ignore_list, qr/^fp: /; push @ignore_list, qr/^path: /; push @ignore_list, qr/^level: /; push @ignore_list, qr/^recipient: /; push @ignore_list, qr/^delivered: /; push @ignore_list, qr/^queue_id: /; push @ignore_list, qr/^queue_name: /; push @ignore_list, qr/^user: /; push @ignore_list, qr/^sender: /; push @ignore_list, qr/^offset: /; push @ignore_list, qr/^offset: /; push @ignore_list, qr/^verify stream disconnect/; push @ignore_list, qr/^event_request_timer: /; push @ignore_list, qr/^smtp_sasl_authenticate: /; push @ignore_list, qr/^flush_add: /; push @ignore_list, qr/^disposing SASL state information/; push @ignore_list, qr/^starting new SASL client/; push @ignore_list, qr/^error: dict_ldap_connect: /; push @ignore_list, qr/^error: to submit mail, use the Postfix sendmail command/; push @ignore_list, qr/^local_deliver[:[]/; push @ignore_list, qr/^_sasl_plugin_load /; push @ignore_list, qr/^exp_type: /; push @ignore_list, qr/^wakeup [\dA-Z]/; push @ignore_list, qr/^defer (?:site|transport) /; push @ignore_list, qr/^local: /; push @ignore_list, qr/^exp_from: /; push @ignore_list, qr/^extension: /; push @ignore_list, qr/^owner: /; push @ignore_list, qr/^unmatched: /; push @ignore_list, qr/^domain: /; push @ignore_list, qr/^initializing the client-side TLS engine/; push @ignore_list, qr/^header_token: /; push @ignore_list, qr/^(?:PUSH|POP) boundary/; push @ignore_list, qr/^recipient limit \d+$/; push @ignore_list, qr/^scan_dir_next: found/; push @ignore_list, qr/^open (?:btree|incoming)/; push @ignore_list, qr/^Renamed to match inode number/; push @ignore_list, qr/^cleanup_[^:]+:/; push @ignore_list, qr/^(?:before|after) input_transp_cleanup: /; push @ignore_list, qr/^event_enable_read: /; push @ignore_list, qr/^report recipient to all milters /; push @ignore_list, qr/_action = defer_if_permit$/; push @ignore_list, qr/^reject_invalid_hostname: /; push @ignore_list, qr/^cfg_get_/; push @ignore_list, qr/^sacl_check: /; # non-anchored #push @ignore_list, qr/: Greylisted for /; push @ignore_list, qr/certificate verification (?:depth|failed for)/; push @ignore_list, qr/re-using session with untrusted certificate, look for details earlier in the log$/; push @ignore_list, qr/socket: wanted attribute: /; push @ignore_list, qr/ smtpd cache$/; push @ignore_list, qr/ old session$/; push @ignore_list, qr/fingerprint=/; push @ignore_list, qr/TLS cipher list "/; } # Evaluates a given line against the list of ignore patterns. # sub in_ignore_list($) { my $line = shift; foreach (@ignore_list) { #return 1 if $line =~ /$_/; if ($line =~ /$_/) { #$IGNORED{$_}++; return 1; } } return 0; } # Accepts common fields from a standard delivery attempt, processing then # and returning modified values # sub process_delivery_attempt ($ $ $ $) { my ($to,$origto,$DDD,$reason) = @_; $reason =~ s/\((.*)\)/$1/; # Makes capturing nested parens easier # leave $to/$origto undefined, or strip < > chars if not null address (<>). defined $to and $to = ($to eq '') ? '<>' : lc $to; defined $origto and $origto = ($origto eq '') ? '<>' : lc $origto; my ($localpart, $domainpart) = split ('@', $to); ($localpart, $domainpart) = ($to, '*unspecified') if ($domainpart eq ''); my ($dsn); # If recipient_delimiter is set, break localpart into user + extension # and save localpart in origto if origto is empty # if ($Opts{'recipient_delimiter'} and $localpart =~ /\Q$Opts{'recipient_delimiter'}\E/o) { # special cases: never split mailer-daemon or double-bounce # or owner- or -request if delim is "-" (dash). unless ($localpart =~ /^(?:mailer-daemon|double-bounce)$/i or ($Opts{'recipient_delimiter'} eq '-' and $localpart =~ /^owner-.|.-request$/i)) { my ($user,$extension) = split (/\Q$Opts{'recipient_delimiter'}\E/, $localpart, 2); $origto = $localpart if ($origto eq ''); $localpart = $user; } } unless (($dsn) = ($DDD =~ /dsn=(\d\.\d+\.\d+)/o)) { $dsn = ''; } if ($Collecting{'delays'} and $DDD =~ m{delay=([\d.]+)(?:, delays=([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+))?}) { # Message delivery time stamps # delays=a/b/c/d, where # a = time before queue manager, including message transmission # b = time in queue manager # c = connection setup including DNS, HELO and TLS; # d = message transmission time. if (defined $2) { $Delays{'1: Before qmgr'}{$2}++; $Delays{'2: In qmgr'}{$3}++; $Delays{'3: Conn setup'}{$4}++; $Delays{'4: Transmission'}{$5}++; } $Delays{'5: Total'}{$1}++; } return ($to,$origto,$localpart,$domainpart,$dsn,$reason); } # Processes postfix/bounce messages # sub postfix_bounce($) { my $line = shift; my $type; $line =~ s/^(?:$re_QID): //o; if ($line =~ /^(sender|postmaster) non-delivery notification/o) { #TDbQ postmaster non-delivery notification: 7446BCD68 #TDbQ sender non-delivery notification: 7446BCD68 $type = 'Non-delivery'; } elsif ($line =~ /^(sender|postmaster) delivery status notification/o ) { #TDbQ sender delivery status notification: 7446BCD68 $type = 'Delivery'; } elsif ($line =~ /^(sender|postmaster) delay notification: /o) { #TDbQ sender delay notification: AA61EC2F9A $type = 'Delayed'; } else { inc_unmatched('bounce') if ! in_ignore_list($line); return; } $Totals{'notificationsent'}++; return unless ($Collecting{'notificationsent'}); $Counts{'notificationsent'}{$type}{$1}++; } # Processes postfix/cleanup messages # cleanup always has a QID # sub postfix_cleanup($) { my $line = shift; my ($qid,$reply,$fmthost,$reject_name); ($qid, $line) = ($1, $2) if ($line =~ /^($re_QID): (.*)$/o ); #TDcQ message-id= return if ($line =~ /^message-id=/); # milter-reject, milter-hold, milter-discard if ($line =~ s/^milter-//) { milter_common($line); return; } ### cleanup bounced messages (always_bcc, recipient_bcc_maps, sender_bcc_maps) # Note: Bounce # See same code elsewhere "Note: Bounce" #TDcQ to=, relay=none, delay=0.11, delays=0.11/0/0/0, dsn=5.7.1, status=bounced optional text... #TDcQ to=, orig_to=, relay=none, delay=0.13, delays=0.13/0/0/0, dsn=5.7.1, status=bounced optional text... if ($line =~ /^to=<(.*?)>,(?: orig_to=<(.*?)>,)? relay=([^,]*).*, ($re_DDD), status=([^ ]+) (.*)$/o) { # ($to,$origto,$relay,$DDD,$status,$reason) = ($1,$2,$3,$4,$5,$6); ### deliverable messages reported by postfix/cleanup #TDcQ to=, relay=none, delay=0, delays=0.1/0/0/0, dsn=2.0.0, status=deliverable optional text... # if ($5 eq 'deliverable') { my ($to,$origto,$relay,$DDD,$reason) = ($1,$2,$3,$4,$6); my ($localpart,$domainpart,$dsn); ($to,$origto,$localpart,$domainpart,$dsn,$reason) = process_delivery_attempt ($to,$origto,$DDD,$reason); $Counts{'deliverable'}{$dsn}{$reason}{$origto ? "$to ($origto)" : $to}++; return; } if ($5 ne 'bounced' and $5 ne 'SOFTBOUNCE') { inc_unmatched('cleanupbounce'); return; } my ($to,$origto,$relay,$DDD,$reason) = ($1,$2,$3,$4,$6); my ($localpart,$domainpart,$dsn); ($to,$origto,$localpart,$domainpart,$dsn,$reason) = process_delivery_attempt ($to,$origto,$DDD,$reason); ### local bounce # XXX local v. remote bounce seems iffy, relative if ($relay =~ /^(?:none|local|virtual|maildrop|127\.0\.0\.1|avcheck)/) { $Totals{'bouncelocal'}++; return unless ($Collecting{'bouncelocal'}); $Counts{'bouncelocal'}{get_dsn_msg($dsn)}{$domainpart}{ucfirst($reason)}{$localpart}++; } ### remote bounce else { ($reply,$fmthost) = cleanhostreply($reason,$relay,$to ne '' ? $to : '<>',$domainpart); $Totals{'bounceremote'}++; return unless ($Collecting{'bounceremote'}); $Counts{'bounceremote'}{get_dsn_msg($dsn)}{$domainpart}{$localpart}{$fmthost}{$reply}++; } } # *header_checks and body_checks elsif (header_body_checks($line)) { #print "cleanup: header_body_checks\n"; return; } #TDcQ resent-message-id=4739073.1 #TDcQ resent-message-id= #TDcQ resent-message-id=? <120B11@samplepc> elsif ( ($line =~ /^resent-message-id=?$/o )) { $Totals{'resent'}++; } #TDcN unable to dlopen .../sasl2/libplain.so.2: .../sasl2/libplain.so.2: failed to map segment from shared object: Operation not permitted elsif ($line =~ /^unable to dlopen /) { # strip extraneous doubling of library path $line = "$1$2 $3" if ($line =~ /(unable to dlopen )([^:]+: )\2(.+)$/); postfix_warning($line); } # postfix >= 3.10 # line=removed (reason) # elsif ($line =~ /^removed/) { $Totals{'removedfromqueue'}++; } else { inc_unmatched('cleanup(last)') if ! in_ignore_list($line); } } =pod header_body_checks Handle cleanup's header_checks and body_checks, and smtp's smtp_body_checks/smtp_*header_checks Possible actions that log are: REJECT optional text... DISCARD optional text... FILTER transport:destination HOLD optional text... REDIRECT user@domain PREPEND text... REPLACE text... WARN optional text... DUNNO and IGNORE are not logged Returns: 1: if line matched or handled 0: otherwise =cut sub header_body_checks($) { my $line = shift; # bcc, discard, filter, hold, prepend, redirect, reject, replace, warning return 0 if ($line !~ /^[bdfhprw]/) or # short circuit alternation when no match possible ($line !~ /^(re(?:ject|direct|place)|filter|hold|discard|prepend|warning|bcc): (header|body|content) (.*)$/); my ($action,$part,$p3) = ($1,$2,$3); #print "header_body_checks: action: \"$action\", part: \"$part\", p3: \"$p3\"\n"; my ($trigger,$host,$eto,$p4,$fmthost,$reject_name); # $re_QID: reject: body ... # $re_QID: reject: header ... # $re_QID: reject: content ... if ($p3 =~ /^(.*) from ([^;]+); from=<.*?>(?: to=<(.*?)>)?(?: proto=\S*)?(?: helo=<.*?>)?(?:: (.*)|$)/) { ($trigger,$host,$eto,$p4) = ($1,$2,$3,$4); # $action $part $trigger $host $eto $p4 #TDcQ reject: body Subject: Cheap cialis from local; from=: optional text... #TDcQ reject: body Quality replica watches!!! from hb.example.com[10.0.0.1]; from= to= proto=SMTP helo=: optional text... #TDcQ reject: header To: from hb.example.com[10.0.0.1]; from= to= proto=ESMTP helo=: optional text... # message_reject_characters (postfix >= 2.3) #TDcQ reject: content Received: by example.com Postfix from example.com[10.0.0.1]; from= to= proto=ESMTP helo=.example.com>: 5.7.1 disallowed character #TDcQ filter: header To: to@example.com from hb.example.com[10.0.0.1]; from= to= proto=ESMTP helo=: transport:destination #TDcQ hold: header Message-ID: from localhost[127.0.0.1]; from= to= proto=ESMTP helo=: optional text... #TDcQ hold: header Subject: Hold Test from local; from= to=: optional text... #TDcQ hold: header Received: by example.com...from x from local; from= #TDcQ hold: header Received: from x.com (x.com[10.0.0.1])??by example.com (Postfix) with ESMTP id 630BF??for ; Thu, 20 Oct 2006 13:27: from example.com[10.0.0.1]; from= to= proto=ESMTP helo= # 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= proto=SMTP helo=: faked header #TDcQ redirect: header From: "Attn Men" from hb.example.com[10.0.0.1]; from= to= proto=ESMTP helo=: user@domain #TDcQ redirect: header From: "Superman" from hb.example.com[10.0.0.2]; from= to= proto=ESMTP helo=: user@domain #TDcQ redirect: body Original drugs from hb.example.com[10.0.0.1]; from= to= proto=SMTP helo=: user@domain #TDcQ discard: header Subject: **SPAM** Blah... from hb.example.com[10.0.0.1]; from= to= proto=ESMTP helo= #TDcQ prepend: header Rubble: Mr. from localhost[127.0.0.1]; from= to= proto=ESMTP helo=: text... #TDcQ replace: header Rubble: flintstone from localhost[127.0.0.1]; from= to= proto=ESMTP helo=: text... #TDcQ warning: header Date: Tues, 99:34:67 from localhost[127.0.0.1]; from= to= proto=ESMTP helo=: optional text... # BCC action (2.6 experimental branch) #TDcQ bcc: header To: to@example.com from hb.example.com[10.0.0.1]; from= to= proto=ESMTP helo=: user@domain # Note: reject_warning does not seem to occur } else { # smtp_body_checks, smtp_header_checks, smtp_mime_header_checks, smtp_nested_header_checks (postfix >= 2.5) #TDsQ replace: header Sender: : Sender: $trigger = $p3; $host = ''; $eto = ''; $p4 = $part eq 'body' ? 'smtp_body_checks' : 'smtp_*header_checks'; #inc_unmatched('header_body_checks'); #return 1; } #print " trigger: \"$trigger\", host: \"$host\", eto: \"$eto\", p4: \"$p4\"\n"; $trigger =~ s/\s+/ /g; $trigger = '*unknown reason' if ($trigger eq ''); $eto = '*unknown' if ($eto eq ''); my ($trig,$trig_opt,$text); if ($part eq 'header') { ($trig = $trigger) =~ s/^([^:]+:).*$/Header check "$1"/; } elsif ($part eq 'body') { $trig = "Body check"; } else { $trig = "Content check"; } # message_reject_characters (postfix >= 2.3) if ($p4 eq '') { $text = '*generic'; $trig_opt = $trig; } else { $text = $p4; $trig_opt = "$trig ($p4)"; } if ($host eq 'local') { $fmthost = formathost('127.0.0.1', 'local'); } elsif ($host =~ /([^[]+)\[([^]]+)\]/) { $fmthost = formathost($2,$1); } else { $fmthost = '*unknown'; } # Note: Counts # Ensure each $Counts{key} accumulator is consistently # used with the same number of hash key levels throughout the code. # For example, $Counts{'hold'} below has 4 keys; ensure that every # other usage of $Counts{'hold'} also has 4 keys. Currently, it is # OK to set the last key as '', but only the last. if ($action eq 'reject') { $Counts{'byiprejects'}{$fmthost}++ if $Collecting{'byiprejects'}; # Note: no temporary or reject_warning # Note: no reply code - force into a 5xx reject # XXX this won't be seen if the user has no 5.. entry in reject_reply_patterns $Totals{$reject_name = "5xxreject$part" }++; $Counts{$reject_name}{$text}{$eto}{$fmthost}{$trigger}++ if $Collecting{$reject_name}; } elsif ( $action eq 'filter' ) { $Totals{'filtered'}++; $Counts{'filtered'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++ if $Collecting{'filtered'}; } elsif ( $action eq 'hold' ) { $Totals{'hold'}++; $Counts{'hold'}{$trig_opt}{$fmthost}{$eto}{$trigger}++ if $Collecting{'hold'}; } elsif ( $action eq 'redirect' ) { $Totals{'redirected'}++; $Counts{'redirected'}{$trig}{$text}{$eto}{$fmthost}{$trigger}++ if $Collecting{'redirected'}; } elsif ( $action eq 'discard' ) { $Totals{'discarded'}++; $Counts{'discarded'}{$trig}{$fmthost}{$eto}{$trigger}++ if $Collecting{'discarded'}; } elsif ( $action eq 'prepend' ) { $Totals{'prepended'}++; $Counts{'prepended'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++ if $Collecting{'prepended'}; } elsif ( $action eq 'replace' ) { $Totals{'replaced'}++; $Counts{'replaced'}{"$trig ($text)"}{$fmthost}{$eto}{$trigger}++ if $Collecting{'replaced'}; } elsif ( $action eq 'warning' ) { $Totals{'warned'}++; $Counts{'warned'}{$trig}{$fmthost}{$eto}{$trigger}++ if $Collecting{'warned'}; } elsif ( $action eq 'bcc' ) { $Totals{'bcced'}++; $Counts{'bcced'}{$text}{$trig}{$trigger}{$eto}{$fmthost}++ if $Collecting{'bcced'}; } else { inc_unmatched('header_body_checks unexpected action'); } return 1; } # Handle common milter actions: # milter-reject, milter-hold, milter-discard # which are created by both smtpd and cleanup # sub milter_common($) { my $line = shift; #TDsdN milter-reject: MAIL from milterS.example.com[10.0.0.1]: 553 5.1.7 address incomplete; proto=ESMTP helo= #TDsdN milter-reject: CONNECT from milterS.example.com[10.0.0.2]: 451 4.7.1 Service unavailable - try again later; proto=SMTP #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= to= proto=ESMTP helo= #TDsdQ milter-hold: END-OF-MESSAGE from milterS.example.com[10.0.0.4]: milter triggers HOLD action; from= to= proto=ESMTP helo= #TDcQ milter-reject: END-OF-MESSAGE from milterC.example.com[10.0.0.1]: 5.7.1 Some problem; from= to= proto=SMTP helo= #TDcQ milter-reject: CONNECT from milterC.example.com[10.0.0.2]: 5.7.1 Some problem; proto=SMTP #TDcQ milter-hold: END-OF-MESSAGE from milterC.example.com[10.0.0.3]: milter triggers HOLD action; from= to= proto=ESMTP helo= #TDcQ milter-discard: END-OF-MESSAGE from milterC.example.com[10.0.0.4]: milter triggers DISCARD action; from= to= proto=ESMTP helo= # 84B82AC8B3: milter-reject: END-OF-MESSAGE from localhost[127.0.0.1]: 5.7.1 Blocked my ($efrom,$eto,$proto,$helo) = strip_ftph($line); #print "efrom: '$efrom', eto: '$eto', proto: '$proto', helo: '$helo'\n"; $line =~ s/;$//; if ($line =~ /^(reject|hold|discard): (\S+) from ([^[]+)\[([^]]+)\](?::\d+)?: (.*)$/) { my ($action,$stage,$host,$hostip,$reply) = ($1,$2,$3,$4,$5); #print "action: '$action', stage: '$stage', host: '$host', hostip: '$hostip', reply: '$reply'\n"; if ($action eq 'reject') { my ($dsn,$fmthost,$reject_name); ($dsn,$reply) = ($1,$2) if $reply =~ /^($re_DSN) (.*)$/o; #print " dsn: '$dsn', reply: '$reply'\n"; if ($Collecting{'byiprejects'} and substr($dsn,0,1) eq '5') { $fmthost = formathost($hostip,$host); $Counts{'byiprejects'}{$fmthost}++; } # Note: reject_warning does not seem to occur # Note: See rejectmilter elsewhere $Totals{$reject_name = get_reject_key($dsn) . 'rejectmilter' }++; return unless ($Collecting{$reject_name}); $Counts{$reject_name}{$stage}{$fmthost ? $fmthost : formathost($hostip,$host)}{$reply}++; } # milter-hold elsif ($action eq 'hold') { $Totals{'hold'}++; return unless ($Collecting{'hold'}); $Counts{'hold'}{'milter'}{$stage}{formathost($hostip,$host)}{$eto}++; } # milter-discard else { # $action eq 'discard' $Totals{'discarded'}++; return unless ($Collecting{'discarded'}); $Counts{'discarded'}{'milter'}{$stage}{formathost($hostip,$host)}{$eto}++; } } else { inc_unmatched('milter_common)'); } } sub postfix_dnsblog { my $line = shift; #postfix/dnsblog[16943]: addr 192.168.0.1 listed by domain bl.spamcop.net as 127.0.0.2 #postfix/dnsblog[78598]: addr 192.168.0.1 blocked by domain zen.spamhaus.org as 127.0.0.11 if ($line =~ /^addr (\S+) (?:listed|blocked) by domain (\S+) as (\S+)$/) { $Counts{'dnsblog'}{$2}{$1}{$3}++ if $Collecting{'dnsblog'}; } else { inc_unmatched('dnsblog') if ! in_ignore_list($line); return; } } sub postfix_postscreen { my $line = shift; return if ( $line =~ /^cache / or $line =~ /discarding EHLO keywords: / or $line =~ /: discard_mask / or $line =~ /: sq=\d+ cq=\d+ event/ or $line =~ /: replacing command "/ ); if (($line =~ /^(PREGREET) \d+ (?:after \S+)? from \[([^]]+)\](?::\d+)?/) or # PREGREET 20 after 0.31 from [192.168.0.1]:12345: HELO 10.0.0.1?? # HANGUP after 0.7 from [192.168.0.4]:12345 ($line =~ /^(HANGUP) (?:after \S+)? from \[([^]]+)\](?::\d+)?/)) { $Counts{'postscreen'}{lc $1}{$2}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^(WHITELISTED|BLACKLISTED|PASS \S+) \[([^]]+)\](?::\d+)?$/) { # PASS NEW [192.168.0.2]:12345 # PASS OLD [192.168.0.3]:12345 $Counts{'postscreen'}{lc $1}{$2}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^DNSBL rank (\S+) for \[([^]]+)\](?::\d+)?$/) { $Counts{'postscreen'}{'dnsbl'}{$2}{$1}++ if $Collecting{'postscreen'}; $Counts{'dnsblranks'}{$1}{$2}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^(CONNECT|COMMAND (?:(?:TIME|COUNT|LENGTH) LIMIT|PIPELINING)|NON-SMTP COMMAND|BARE NEWLINE) from \[([^\]]+)\]:\d+/) { # CONNECT from [192.168.1.1]:12345 $Counts{'postscreen'}{lc($1)}{$2}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^DISCONNECT \[([^\]]+)\]:\d+$/) { # DISCONNECT [192.168.1.1]:12345 $Counts{'postscreen'}{'disconnect'}{$1}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^NOQUEUE: reject: RCPT from \[([^]]+)\](?::\d+)?: ($re_DSN) ([^;]+)/o) { #NOQUEUE: reject: RCPT from [192.168.0.1]:12345: 550 5.7.1 Service unavailable; client [192.168.0.1] blocked using b.barracudacentral.org; from=, to=, proto=SMTP, helo= my ($ip,$dsn,$msg) = ($1,$2,$3); if ($dsn =~ /^([54])/) { $Counts{'postscreen'}{$1 . 'xx reject'}{"$dsn $msg"}{$ip}++ if $Collecting{'postscreen'}; } else { $Counts{'postscreen'}{'reject'}{"$dsn $msg"}{$ip}{$END_KEY}++ if $Collecting{'postscreen'}; } } elsif ($line =~ /^NOQUEUE: reject: CONNECT from \[([^]]+)\](?::\d+)?: too many connections/) { # NOQUEUE: reject: CONNECT from [192.168.0.1]:7197: too many connections $Counts{'postscreen'}{'reject'}{'Too many connections'}{$1}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^reject: connect from \[([^]]+)\](?::\d+)?: (.+)$/) { # reject: connect from [192.168.0.1]:21225: all screening ports busy $Counts{'postscreen'}{'reject'}{"\u$2"}{$1}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^(?:WHITELIST VETO) \[([^]]+)\](?::\d+)?$/) { # WHITELIST VETO [192.168.0.8]:43579 $Counts{'postscreen'}{'whitelist veto'}{$1}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^(entering|leaving) STRESS mode with (\d+) connections$/) { # entering STRESS mode with 90 connections $Counts{'postscreen'}{'stress mode: ' . $1}{$2}{$END_KEY}++ if $Collecting{'postscreen'}; } elsif ($line =~ /^close database (\S+): No such file or directory/) { # close database /var/lib/postfix/postscreen_cache.db: No such file or directory (possible Berkeley DB bug) $Counts{'postscreen'}{'close database'}{$1}{$END_KEY}++ if $Collecting{'postscreen'}; } else { inc_unmatched('postscreen') if ! in_ignore_list($line); return; } $Totals{'postscreen'}++; } # Handles postfix/postsuper lines # sub postfix_postsuper($) { my $line = shift; return if $line =~ /^Deleted: \d+ messages?$/; if ($line =~ /^Placed on hold: (\d+) messages?$/o) { #TDps Placed on hold: 2 messages # Note: See Hold elsewhere $Totals{'hold'} += $1; return unless ($Collecting{'hold'}); $Counts{'hold'}{'Postsuper'}{'localhost'}{"bulk hold: $1"}{''} += $1; } elsif ($line =~ /^Released from hold: (\d+) messages?$/o) { #TDps Released from hold: 1 message $Totals{'releasedfromhold'} += $1; } elsif ($line =~ /^Requeued: (\d+) messages?$/o) { #TDps Requeued: 1 message $Totals{'requeued'} += $1; } elsif (my($qid,$p2) = ($line =~ /($re_QID): (.*)$/)) { # postsuper double reports the following 3 lines return if ($p2 eq 'released from hold'); return if ($p2 eq 'placed on hold'); return if ($p2 eq 'requeued'); if ($p2 =~ /^removed\s*$/o) { # Note: See REMOVED elsewhere # 52CBDC2E0F: removed delete $SizeByQid{$qid} if (exists $SizeByQid{$qid}); $Totals{'removedfromqueue'}++; } elsif (! in_ignore_list ($p2)) { inc_unmatched('postsuper2'); } } elsif (! in_ignore_list ($line)) { inc_unmatched('postsuper1'); } } # Handles postfix panic: lines # sub postfix_panic($) { #TD panic: myfree: corrupt or unallocated memory block $Totals{'panicerror'}++; return unless ($Collecting{'panicerror'}); $Counts{'panicerror'}{ucfirst($1)}++; } # Handles postfix fatal: lines # sub postfix_fatal($) { my ($reason) = shift; if ($reason =~ /^\S*\(\d+\): Message file too big$/o) { #TD fatal: root(0): Message file too big $Totals{'fatalfiletoobig'}++; # XXX its not clear this is at all useful - consider falling through to last case } elsif ( $reason =~ /^config variable (\S*): (.*)$/o ) { #TD fatal: config variable inet_interfaces: host not found: 10.0.0.1:2525 #TD fatal: config variable inet_interfaces: host not found: all:2525 $Totals{'fatalconfigerror'}++; return unless ($Collecting{'fatalconfigerror'}); $Counts{'fatalconfigerror'}{ucfirst($reason)}++; } else { #TD fatal: watchdog timeout #TD fatal: bad boolean configuration: smtpd_use_tls = #TDvN fatal: update queue file active/4B709F060E: File too large $reason =~ s/(^update queue file \w+\/)\w+:/$1*:/; $Totals{'fatalerror'}++; return unless ($Collecting{'fatalerror'}); $Counts{'fatalerror'}{ucfirst($reason)}++; } } # Handles postfix fatal: lines # sub postfix_error($) { my ($reason) = shift; # postfix/postfix-script[4271]: error: unknown command: 'rel' $Totals{'error'}++; return unless ($Collecting{'fatalerror'}); $Counts{'error'}{ucfirst($reason)}++; } # Handles postfix warning: lines # and additional lines coerced into warnings # sub postfix_warning($) { my ($warning) = shift; # Skip these return if ($warning =~ /$re_QID: skipping further client input$/o); return if ($warning =~ /^Mail system is down -- accessing queue directly$/o); return if ($warning =~ /^SASL authentication failure: (?:Password verification failed|no secret in database)$/o); return if ($warning =~ /^no MX host for .* has a valid A record$/o); return if ($warning =~ /^uid=\d+: Broken pipe$/o); #TD warning: connect to 127.0.0.1:12525: Connection refused #TD warning: problem talking to server 127.0.0.1:12525: Connection refused #TD warning: valid_ipv4_hostaddr: invalid octet count: my ($domain,$to,$type,$site,$helo,$cmd); my ($addr,$size,$hostip,$host,$port,$reason,$qid,$queue,$reason2,$process,$status,$service); if (($hostip,$host,$reason) = ($warning =~ /^(?:smtpd_peer_init: )?([^:]+): hostname ([^ ]+) verification failed: (.*)$/) or ($hostip,$reason,$host) = ($warning =~ /^(?:smtpd_peer_init: )?([^:]+): (address not listed for hostname) (.*)$/) or ($host,$reason,$hostip,$reason2) = ($warning =~ /^(?:smtpd_peer_init: )?hostname (\S+) (does not resolve to address) ([\d.]+)(: host not found, try again)?$/)) { #TD warning: 10.0.0.1: hostname sample.com verification failed: Host not found #TD warning: smtpd_peer_init: 192.168.0.1: hostname example.com verification failed: Name or service not known #TD warning: 192.168.0.1: address not listed for hostname sample.net # post 2.8 #TD warning: hostname 281.example.net does not resolve to address 192.168.0.1: host not found, try again #TD warning: hostname 281.example.net does not resolve to address 192.168.0.1 $reason .= $reason2 if $reason2; $Totals{'hostnameverification'}++; return unless ($Collecting{'hostnameverification'}); $Counts{'hostnameverification'}{ucfirst($reason)}{formathost($hostip,$host)}++; } elsif (($warning =~ /^$re_QID: queue file size limit exceeded$/o) or ($warning =~ /^uid=\d+: File too large$/o)) { $Totals{'warnfiletoobig'}++; } elsif ($warning =~ /^database (?:[^ ]*) is older than source file ([\w\/]+)$/o) { #TD warning: database /etc/postfix/client_checks.db is older than source file /etc/postfix/client_checks $Totals{'databasegeneration'}++; return unless ($Collecting{'databasegeneration'}); $Counts{'databasegeneration'}{$1}++; } elsif (($reason,$qid,$reason2) = ($warning =~ /^(open active) ($re_QID): (.*)$/o) or ($reason,$qid,$reason2) = ($warning =~ /^qmgr_active_corrupt: (save corrupt file queue active) id ($re_QID): (.*)$/o) or ($qid,$reason,$reason2) = ($warning =~ /^($re_QID): (write queue file): (.*)$/o)) { #TD warning: open active BDB9B1309F7: No such file or directory #TD warning: qmgr_active_corrupt: save corrupt file queue active id 4F4272F342: No such file or directory #TD warning: E669DE52: write queue file: No such file or directory $Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'}); $Counts{'queuewriteerror'}{"$reason: $reason2"}{$qid}++; } elsif (($qid,$reason) = ($warning =~ /^qmgr_active_done_3_generic: remove ($re_QID) from active: (.*)$/o)) { #TD warning: qmgr_active_done_3_generic: remove AF0F223FC05 from active: No such file or directory $Totals{'queuewriteerror'}++; return unless ($Collecting{'queuewriteerror'}); $Counts{'queuewriteerror'}{"remove from active: $reason"}{$qid}++; } elsif (($queue,$qid) = ($warning =~ /^([^\/]*)\/($re_QID): Error writing message file$/o )) { #TD warning: maildrop/C9E66ADF: Error writing message file $Totals{'messagewriteerror'}++; return unless ($Collecting{'messagewriteerror'}); $Counts{'messagewriteerror'}{$queue}{$qid}++; } elsif (($process,$status) = ($warning =~ /^process ([^ ]*) pid \d+ exit status (\d+)$/o)) { #TD warning: process /usr/lib/postfix/smtp pid 9724 exit status 1 $Totals{'processexit'}++; return unless ($Collecting{'processexit'}); $Counts{'processexit'}{"Exit status $status"}{$process}++; } elsif ($warning =~ /^mailer loop: (.*)$/o) { #TD warning: mailer loop: best MX host for example.com is local $Totals{'mailerloop'}++; return unless ($Collecting{'mailerloop'}); $Counts{'mailerloop'}{$1}++; } elsif ($warning =~ /^no (\S+) host for (\S+) has a valid address record$/) { #TDs warning: no MX host for example.com has a valid address record $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'}); $Counts{'dnserror'}{"No $1 host has a valid address record"}{$2}{$END_KEY}++; } elsif ($warning =~ /^(Unable to look up \S+ host) (.+)$/) { #TDsd warning: Unable to look up MX host for example.com: Host not found #TDsd warning: Unable to look up MX host mail.example.com for Sender address from@example.com: hostname nor servname provided, or not known #TDsd warning: Unable to look up NS host ns1.example.local for Sender address bounce@example.local: No address associated with hostname $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'}); my ($problem,$target,$reason) = ($1, split(/: /,$2)); $reason =~ s/, try again//; if ($target =~ /^for (\S+)$/) { $Counts{'dnserror'}{$problem}{ucfirst($reason)}{$1}{$END_KEY}++; } elsif ($target =~ /^(\S+)( for \S+ address) (\S+)$/) { $Counts{'dnserror'}{$problem . lc($2)}{ucfirst($reason)}{$1}{$3}++; } } elsif ($warning =~ /^((?:malformed|numeric) domain name in .+? of \S+ record) for (.*):(.*)?$/) { my ($problem,$domain,$reason) = ($1,$2,$3); #TDsd warning: malformed domain name in resource data of MX record for example.com: #TDsd warning: malformed domain name in resource data of MX record for example.com: mail.example.com\\032 #TDsd warning: numeric domain name in resource data of MX record for sample.com: 192.168.0.1 $Totals{'dnserror'}++; return unless ($Collecting{'dnserror'}); $Counts{'dnserror'}{ucfirst($problem)}{$domain}{$reason eq '' ? '*unknown' : $reason}{$END_KEY}++; } elsif ($warning =~ /^numeric hostname: ([\S]+)$/) { #TD warning: numeric hostname: 192.168.0.1 $Totals{'numerichostname'}++; return unless ($Collecting{'numerichostname'}); $Counts{'numerichostname'}{$1}++; } elsif ( ($host,$hostip,$port,$type,$reason) = ($warning =~ /^([^[]+)\[([^]]+)\](?::(\d+))? (sent \w+ header instead of SMTP command): (.*)$/) or ($type,$host,$hostip,$port,$reason) = ($warning =~ /^(non-E?SMTP command) from ([^[]+)\[([^]]+)\](?::(\d+))?: (.*)$/) or ($type,$host,$hostip,$port,$reason) = ($warning =~ /^(?:$re_QID: )?(non-E?SMTP response) from ([^[]+)\[([^]]+)\](?::(\d+))?:(?: (.*))?$/o)) { # ancient #TDsd warning: example.com[192.168.0.1] sent message header instead of SMTP command: From: "Someone" <40245426501example.com> # current #TDsd warning: non-SMTP command from sample.net[10.0.0.1]: Received: from 192.168.0.1 (HELO bogus.sample.com) #TDs warning: 6B01A8DEF: non-ESMTP response from mail.example.com[192.168.0.1]:25: $Totals{'smtpconversationerror'}++; return unless ($Collecting{'smtpconversationerror'}); $host .= ' :' . $port if ($port and $port ne '25'); $Counts{'smtpconversationerror'}{ucfirst($type)}{formathost($hostip,$host)}{$reason}++; } elsif ($warning =~ /^valid_hostname: (.*)$/o) { #TD warning: valid_hostname: empty hostname $Totals{'hostnamevalidationerror'}++; return unless ($Collecting{'hostnamevalidationerror'}); $Counts{'hostnamevalidationerror'}{$1}++; } elsif (($host,$hostip,$type,$reason) = ($warning =~ /^([^[]+)\[([^]]+)\](?::\d+)?: SASL (.*) authentication failed(.*)$/)) { #TDsd warning: unknown[10.0.0.1]: SASL LOGIN authentication failed: bad protocol / cancel #TDsd warning: example.com[192.168.0.1]: SASL DIGEST-MD5 authentication failed # see saslauthfail elsewhere $Totals{'saslauthfail'}++; return unless ($Collecting{'saslauthfail'}); if ($reason) { $reason = $type . $reason; } else { $reason = $type; } $Counts{'saslauthfail'}{$reason}{formathost($hostip,$host)}++; } elsif (($host,$reason) = ($warning =~ /^(\S+): RBL lookup error:.* Name service error for (?:name=)?\1(?: type=[^:]+)?: (.*)$/o)) { #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 #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 $Totals{'rblerror'}++; return unless ($Collecting{'rblerror'}); $Counts{'rblerror'}{$reason}{$host}++; } elsif ( ($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[([^]]+)\](?::\d+)? (greeted me with my own hostname) ([^ ]*)$/ ) or ($host,$hostip,$reason,$helo) = ($warning =~ /^host ([^[]+)\[([^]]+)\](?::\d+)? (replied to HELO\/EHLO with my own hostname) ([^ ]*)$/ )) { #TDs warning: host example.com[192.168.0.1] greeted me with my own hostname example.com #TDs warning: host example.com[192.168.0.1] replied to HELO/EHLO with my own hostname example.com $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'}); $Counts{'heloerror'}{ucfirst($reason)}{formathost($hostip,$host)}++; } elsif (($size,$host,$hostip) = ($warning =~ /^bad size limit "([^"]+)" in EHLO reply from ([^[]+)\[([^]]+)\](?::\d+)?$/ )) { #TD warning: bad size limit "-679215104" in EHLO reply from example.com[192.168.0.1] $Totals{'heloerror'}++; return unless ($Collecting{'heloerror'}); $Counts{'heloerror'}{"Bad size limit in EHLO reply"}{formathost($hostip,$host)}{"$size"}++; } elsif ( ($host,$hostip,$cmd,$addr) = ($warning =~ /^Illegal address syntax from ([^[]+)\[([^]]+)\](?::\d+)? in ([^ ]*) command: (.*)/ )) { #TD warning: Illegal address syntax from example.com[192.168.0.1] in MAIL command: user@sample.net $addr =~ s/[<>]//g unless ($addr eq '<>'); $Totals{'illegaladdrsyntax'}++; return unless ($Collecting{'illegaladdrsyntax'}); $Counts{'illegaladdrsyntax'}{$cmd}{$addr}{formathost($hostip,$host)}++; } elsif ($warning =~ /^(timeout|premature end-of-input) on (.+) while reading (.*)$/o or $warning =~ /^(malformed (?:base64|numerical)|unexpected end-of-input) from (.+) while reading (.*)$/o) { #TDs warning: premature end-of-input on private/anvil while reading input attribute name #TDs warning: timeout on private/anvil while reading input attribute data #TDs warning: unexpected end-of-input from 127.0.0.1:10025 socket while reading input attribute name #TDs warning: malformed base64 data from %s while reading input attribute data: ... #TDs warning: malformed numerical data from %s while reading input attribute data: ... $Totals{'attrerror'}++; return unless ($Collecting{'attrerror'}); $Counts{'attrerror'}{$2}{$1}{$3}++; } elsif ($warning =~ /^(.*): (bad command startup -- throttling)/o) { #TD warning: /usr/libexec/postfix/trivial-rewrite: bad command startup -- throttling $Totals{'startuperror'}++; return unless ($Collecting{'startuperror'}); $Counts{'startuperror'}{ucfirst($2)}{$1}++; } elsif ($warning =~ /(problem talking to service [^:]*): (.*)$/o) { #TD warning: problem talking to service rewrite: Connection reset by peer #TD warning: problem talking to service rewrite: Success $Totals{'communicationerror'}++; return unless ($Collecting{'communicationerror'}); $Counts{'communicationerror'}{ucfirst($1)}{$2}++; } elsif (my ($map,$key) = ($warning =~ /^$re_QID: ([^ ]*) map lookup problem for (.*)$/o)) { #TD warning: 6F74F74431: virtual_alias_maps map lookup problem for root@example.com $Totals{'mapproblem'}++; return unless ($Collecting{'mapproblem'}); $Counts{'mapproblem'}{$map}{$key}++; } elsif (($map,$reason) = ($warning =~ /^pcre map ([^,]+), (.*)$/o)) { #TD warning: pcre map /etc/postfix/body_checks, line 92: unknown regexp option "F": skipping this rule $Totals{'mapproblem'}++; return unless ($Collecting{'mapproblem'}); $Counts{'mapproblem'}{$map}{$reason}++; } elsif (($reason) = ($warning =~ /dict_ldap_lookup: (.*)$/o)) { #TD warning: dict_ldap_lookup: Search error 80: Internal (implementation specific) error $Totals{'ldaperror'}++; return unless ($Collecting{'ldaperror'}); $Counts{'ldaperror'}{$reason}++; } elsif (($type,$size,$host,$hostip,$service) = ($warning =~ /^(.+) limit exceeded: (\d+) from ([^[]+)\[([^]]+)\](?::\d+)? for service (.*)/ )) { #TDsd warning: Connection concurrency limit exceeded: 51 from example.com[192.168.0.1] for service smtp #TDsd warning: Connection rate limit exceeded: 20 from mail.example.com[192.168.0.1] for service smtp #TDsd warning: Connection rate limit exceeded: 30 from unknown[unknown] for service smtp #TDsd warning: Recipient address rate limit exceeded: 21 from example.com[10.0.0.1] for service smtp #TDsd warning: Message delivery request rate limit exceeded: 11 from example.com[10.0.0.1] for service smtp #TDsd warning: New TLS session rate limit exceeded: 49 from example.com[10.0.0.1] for service smtp $Totals{'anvil'}++; return unless ($Collecting{'anvil'}); $Counts{'anvil'}{$service}{$type}{formathost($hostip,$host)}{$size}++; } elsif (my ($extname,$intname,$limit) = ($warning =~ /service "([^"]+)" \(([^)]+)\) has reached its process limit "([^"]+)":/o)) { #TD warning: service "smtp" (25) has reached its process limit "50": new clients may experience noticeable delays $Totals{'processlimit'}++; return unless ($Collecting{'processlimit'}); $Counts{'processlimit'}{'See http://www.postfix.org/STRESS_README.html'}{"$extname ($intname)"}{$limit}++; } else { #TDsd warning: No server certs available. TLS won't be enabled #TDs warning: smtp_connect_addr: bind : Address already in use # These two messages follow ProcessLimit message above #TDm warning: to avoid this condition, increase the process count in master.cf or reduce the service time per client #TDm warning: see http://www.postfix.org/STRESS_README.html for examples of stress-dependent configuration settings return if ($warning =~ /^to avoid this condition,/o); return if ($warning =~ /^see http:\/\/www\.postfix\.org\/STRESS_README.html/o); #TDsd warning: 009314BD9E: read timeout on cleanup socket $warning =~ s/^$re_QID: (read timeout on \S+ socket)/$1/; #TDsd warning: Read failed in network_biopair_interop with errno=0: num_read=0, want_read=11 #TDs warning: Read failed in network_biopair_interop with errno=0: num_read=0, want_read=11 $warning =~ s/^(Read failed in network_biopair_interop) with .*$/$1/; =cut $warning =~ s/^(TLS library problem: )\d+:(error:.*)$/$1$2/; $warning =~ s/^(network_biopair_interop: error reading) \d+ bytes(.*)$/$1$2/; 1 TLS library problem: 10212:error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher... 1 TLS library problem: 10217:error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher... 1 network_biopair_interop: error reading 1102 bytes from the network: Connection reset by peer 1 network_biopair_interop: error reading 1120 bytes from the network: Connection reset by peer =cut $Totals{'warningsother'}++; return unless ($Collecting{'warningsother'}); $Counts{'warningsother'}{$warning}++; } } # Handles postfix/postfix-script lines # sub postfix_script($) { my $line = shift; return if ($line =~ /^the Postfix mail system is running: PID: /o); if ($line =~ /^starting the Postfix mail system/o) { $Totals{'postfixstart'}++; } elsif ($line =~ /^stopping the Postfix mail system/o) { $Totals{'postfixstop'}++; } elsif ($line =~ /^refreshing the Postfix mail system/o) { $Totals{'postfixrefresh'}++; } elsif ($line =~ /^waiting for the Postfix mail system to terminate/o) { $Totals{'postfixwaiting'}++; } elsif (! in_ignore_list ($line)) { inc_unmatched('postfix_script'); } } # Handles postfix backwards compatibility mode lines # sub backwards_compatible($) { my $line = shift; if ($line =~ /^Postfix is running with backwards-compatible default settings/o) { $Totals{'backwardscompatible'}++; } elsif ($line =~ /^See http.*COMPATIBILITY_README.html for details/o) { $Totals{'backwardscompatible'}++; } elsif ($line =~ /^To disable backwards compatibility use.*/o) { $Totals{'backwardscompatible'}++; } } # Clean up a server's reply, to give some uniformity to reports # sub cleanhostreply($ $ $ $) { my ($hostreply,$relay,$recip,$domain) = @_; my $fmtdhost = ''; my ($r1, $r2, $dsn, $msg, $host, $event); #print "RELAY: $relay, RECIP: $recip, DOMAIN: $domain\n"; #print "HOSTREPLY: \"$hostreply\"\n"; return ('Accepted', '*unknown') if $hostreply =~ /^25\d/o; # Host or domain name not found. Name service error for name=example.com type=MX: Host not found... if ($hostreply =~ /^Host or domain name not found. Name service error for name=([^:]+): Host not found/o) { return ('Host not found', $1); } if (($host,$dsn,$r1) = ($hostreply =~ /host (\S+) said: ($re_DSN)[\- :]*"?(.*)"?$/o)) { # Strip recipient address from host's reply - we already have it in $recip. $r1 =~ s/[<(]?\Q$recip\E[>)]?\W*//ig; # Strip and capture "in reply to XYZ command" from host's reply if ($r1 =~ s/\s*[(]?(?:in reply to (.*) command)[)]?//o) { $r2 = ": $1"; } $r1 =~ s/^Recipient address rejected: //o; # Canonicalize numerous forms of "recipient unknown" if ( $r1 =~ /^user unknown/i or $r1 =~ /^unknown user/i or $r1 =~ /^unknown recipient address/i or $r1 =~ /^invalid recipient/i or $r1 =~ /^recipient unknown/i or $r1 =~ /^sorry, no mailbox here by that name/i or $r1 =~ /^User is unknown/ or $r1 =~ /^User not known/ or $r1 =~ /^MAILBOX NOT FOUND/ or $r1 =~ /^Recipient Rejected: No account by that name here/ or $r1 =~ /^Recipient does not exist here/ or $r1 =~ /The email account that you tried to reach does not exist./ # Google's long mess or $r1 =~ /(?:no such user|user unknown)/i ) { #print "UNKNOWN RECIP: $r1\n"; $r1 = 'Unknown recipient'; } elsif ($r1 =~ /greylisted/oi) { #print "GREYLISTED RECIP: $r1\n"; $r1 = 'Recipient greylisted'; } elsif ($r1 =~ /^Message temporarily deferred - (\d\.\d+\.\d+)\. Please refer to (.+)$/o) { # Yahoo: 421 Message temporarily deferred - 4.16.51. Please refer to http://... (in reply to end of DATA command)) $dsn = "$dsn $1"; $r1 = "see $2"; } elsif ($r1 =~ /^Resources temporarily not available - Please try again later \[#(\d\.\d+\.\d+)\]\.$/o) { #Yahoo 451 Resources temporarily not available - Please try again later [#4.16.5]. $dsn = "$dsn $1"; $r1 = "resources not available"; } elsif ($r1 =~ /^Message temporarily deferred - (\[\d+\])/o) { # Yahoo: 451 Message temporarily deferred - [160] $dsn = "$dsn $1"; $r1 = ''; } } elsif ($hostreply =~ /^connect to (\S+): (.*)$/o) { #print "CONNECT: $hostreply\n"; $host = $1; $r1 = $2; $r1 =~ s/server refused to talk to me/refused/; } elsif ($hostreply =~ /^host (\S+) refused to talk to me: (.*)$/o) { $host = $1; $msg = $2; #print "HOSTREFUSED: $hostreply\n"; #Yahoo: '421 Message from (10.0.0.1) temporarily deferred - 4.16.50. Please refer to http://... if ($msg =~ /^(\d+) Message from \([^)]+\) temporarily deferred - (\d\.\d+\.\d+)\. Please refer to (.+)$/) { $dsn = "$1 $2"; $msg = "see $3"; } #$r1 = join(': ', 'refused', $msg); $r1 = $msg; } elsif ($hostreply =~ /^(delivery temporarily suspended): connect to (\S+): (.*)$/o) { #print "DELIVERY SUSP: $hostreply\n"; $host = $2; $r1 = join(': ', $1, $3); } elsif ($hostreply =~ /^(delivery temporarily suspended: conversation) with (\S+) (.*)$/o) { # delivery temporarily suspended: conversation with example.com[10.0.0.1] timed out while receiving the initial server greeting) #print "DELIVERY SUSP2: $hostreply\n"; $host = $2; $r1 = join(' ', $1, $3); } elsif (($event,$host,$r1) = ($hostreply =~ /^(lost connection|conversation) with (\S+) (.*)$/o)) { #print "LOST conv/conn: $hostreply\n"; $r1 = join(' ',$event,$r1); } elsif ($hostreply =~ /^(.*: \S+maildrop: Unable to create a dot-lock) at .*$/o) { #print "MAILDROP: $hostreply\n"; $r1 = $1; } elsif ($hostreply =~ /^mail for (\S+) loops back to myself/o) { #print "LOOP: $hostreply\n"; $host = $1; $r1 = 'mailer loop'; } elsif ($hostreply =~ /^unable to find primary relay for (\S+)$/o) { #print "NORELAY: $hostreply\n"; $host = $1; $r1 = 'no relay found'; } elsif ($hostreply =~ /^message size \d+ exceeds size limit \d+ of server (\S+)\s*$/o) { #print "TOOBIG: $hostreply\n"; $host = $1; $r1 = 'message too big'; } else { #print "UNMATCH: $hostreply\n"; $r1 = $hostreply; } #print "R1: $r1, R2: $r2\n"; $r1 =~ s/for name=\Q$domain\E //ig; if ($host eq '') { if ($relay =~ /([^[]+)\[([^]]+)\]/) { $fmtdhost = formathost($2,$1); } else { $fmtdhost = '*unknown'; } } elsif ($host =~ /^([^[]+)\[([^]]+)\]/) { $fmtdhost = formathost($2,$1); } else { $fmtdhost = $host; } return (($dsn ? "$dsn " : '' ) . "\u$r1$r2", $fmtdhost); } # Strip and return from, to, proto, and helo information from a log line # From is set to the empty envelope sender <> as necessary, and To is # always lowercased. # # Note: modifies its input for efficiency # sub strip_ftph($) { my ($helo, $proto, $to, $from); #print "strip_ftph: '$_[0]\n"; $helo = ($_[0] =~ s/\s+helo=<(.*?)>\s*$//) == 1 ? $1 : '*unavailable'; $proto = ($_[0] =~ s/\s+proto=(\S+)\s*$//) == 1 ? $1 : '*unavailable'; $to = ($_[0] =~ s/\s+to=<(.*?)>\s*$//) == 1 ? (lc($1) || '<>') : '*unavailable'; $from = ($_[0] =~ s/\s+from=<(.*?)>\s*$//) == 1 ? ( $1 || '<>') : '*unavailable'; #print "helo: $helo, proto: $proto, to: $to, from: $from\n"; #print "strip_ftph: final: '$_[0]'\n"; return ($from,$to,$proto,$helo); } # Initialize the Getopts option list. Requires the Section table to # be built already. # sub init_getopts_table() { print "init_getopts_table: enter\n" if $Opts{'debug'} & Logreporters::D_ARGS; init_getopts_table_common(@supplemental_reports); add_option ('recipient_delimiter=s'); add_option ('delays!'); add_option ('show_delays=i', sub { $Opts{'delays'} = $_[1]; 1; }); add_option ('delays_percentiles=s'); add_option ('reject_reply_patterns=s'); add_option ('ignore_services=s'); add_option ('postgrey_delays!'); add_option ('postgrey_show_delays=i', sub { $Opts{'postgrey_delays'} = $_[1]; 1; }); add_option ('postgrey_delays_percentiles=s'); add_option ('unknown!', sub { $Opts{'unknown'} = $_[1]; 1; }); add_option ('show_unknown=i', sub { $Opts{'unknown'} = $_[1]; 1; }); add_option ('enable_long_queue_ids=i', sub { $Opts{'long_queue_ids'} = $_[1]; 1; }); add_option ('long_queue_ids!'); =pod # aliases and backwards compatibility add_option ('msgsdeferred=s', \$Opts{'deferred'}); add_option ('msgsdelivered=s', \$Opts{'delivered'}); add_option ('msgssent=s', \$Opts{'sent'}); add_option ('msgssentlmtp=s', \$Opts{'sentlmtp'}); add_option ('msgsforwarded=s', \$Opts{'forwarded'}); add_option ('msgsresent=s', \$Opts{'resent'}); add_option ('warn=s', \$Opts{'warned'}); add_option ('held=s', \$Opts{'hold'}); =cut } # Builds the entire @Section table used for data collection # # Each Section entry has as many as six fields: # # 1. Section array reference # 2. Key to %Counts, %Totals accumulator hashes, and %Collecting hash # 3. Output in Detail report? (must also a %Counts accumulator) # 4. Numeric output format specifier for Summary report # 5. Section title for Summary and Detail reports # 6. A hash to a divisor used to calculate the percentage of a total for that key # # Use begin_section_group/end_section_group to create groupings around sections. # # Sections can be freely reordered if desired, but maintain proper group nesting. # # # The reject* entries of this table are dynamic, in that they are built based # upon the value of $Opts{'reject_reply_patterns'}, which can be specified by # either command line or configuration file. This allows various flavors, of # reject sections based on SMTP reply code (eg. 421 45x, 5xx, etc.). Instead # of creating special sections for each reject variant, the primary key of each # reject section could have been the SMTP reply code. However, this would # require special-case processing to distinguish 4xx temporary rejects from 5xx # permanent rejects in various Totals{'totalrejects*'} counts, and in the # Totals{'totalrejects'} tally. # # Sections can be freely reordered if desired. sub build_sect_table() { if ($Opts{'debug'} & Logreporters::D_SECT) { print "build_sect_table: enter\n"; print "\treject patterns: $Opts{'reject_reply_patterns'}\n"; } my $S = \@Sections; # References to these are used in the Sections table below; we'll predeclare them. $Totals{'totalrejects'} = 0; $Totals{'totalrejectswarn'} = 0; $Totals{'totalacceptplusreject'} = 0; # Configuration and critical errors appear first # SECTIONREF, NAME, DETAIL, FMT, TITLE, DIVISOR begin_section_group ($S, 'warnings'); add_section ($S, 'panicerror', 1, 'd', '*Panic: General panic'); add_section ($S, 'fatalfiletoobig', 0, 'd', '*Fatal: Message file too big'); add_section ($S, 'fatalconfigerror', 1, 'd', '*Fatal: Configuration error'); add_section ($S, 'fatalerror', 1, 'd', '*Fatal: General fatal'); add_section ($S, 'error', 1, 'd', '*Error: General error'); add_section ($S, 'processlimit', 1, 'd', '*Warning: Process limit reached, clients may delay'); add_section ($S, 'warnfiletoobig', 0, 'd', '*Warning: Queue file size limit exceeded'); add_section ($S, 'warninsufficientspace', 0, 'd', '*Warning: Insufficient system storage error'); add_section ($S, 'warnconfigerror', 1, 'd', '*Warning: Server configuration error'); add_section ($S, 'queuewriteerror', 1, 'd', '*Warning: Error writing queue file'); add_section ($S, 'messagewriteerror', 1, 'd', '*Warning: Error writing message file'); add_section ($S, 'databasegeneration', 1, 'd', '*Warning: Database is older than source file'); add_section ($S, 'mailerloop', 1, 'd', '*Warning: Mailer loop'); add_section ($S, 'startuperror', 1, 'd', '*Warning: Startup error'); add_section ($S, 'mapproblem', 1, 'd', '*Warning: Map lookup problem'); add_section ($S, 'attrerror', 1, 'd', '*Warning: Error reading attribute data'); add_section ($S, 'anvil', 1, 'd', '*Warning: Anvil limit reached'); add_section ($S, 'processexit', 1, 'd', 'Process exited'); add_section ($S, 'hold', 1, 'd', 'Placed on hold'); add_section ($S, 'communicationerror', 1, 'd', 'Postfix communications error'); add_section ($S, 'saslauthfail', 1, 'd', 'SASL authentication failed'); add_section ($S, 'ldaperror', 1, 'd', 'LDAP error'); add_section ($S, 'warningsother', 1, 'd', 'Miscellaneous warnings'); add_section ($S, 'totalrejectswarn', 0, 'd', 'Reject warnings (warn_if_reject)'); end_section_group ($S, 'warnings'); begin_section_group ($S, 'bytes', "\n"); add_section ($S, 'bytesaccepted', 0, 'Z', 'Bytes accepted '); # Z means print scaled as in 1k, 1m, etc. add_section ($S, 'bytessentsmtp', 0, 'Z', 'Bytes sent via SMTP'); add_section ($S, 'bytessentlmtp', 0, 'Z', 'Bytes sent via LMTP'); add_section ($S, 'bytesdelivered', 0, 'Z', 'Bytes delivered'); add_section ($S, 'bytesforwarded', 0, 'Z', 'Bytes forwarded'); end_section_group ($S, 'bytes', $sep1); begin_section_group ($S, 'acceptreject', "\n"); begin_section_group ($S, 'acceptreject2', "\n"); add_section ($S, 'msgsaccepted', 0, 'd', 'Accepted', \$Totals{'totalacceptplusreject'}); add_section ($S, 'totalrejects', 0, 'd', 'Rejected', \$Totals{'totalacceptplusreject'}); end_section_group ($S, 'acceptreject2', $sep2); add_section ($S, 'totalacceptplusreject', 0, 'd', 'Total', \$Totals{'totalacceptplusreject'}); end_section_group ($S, 'acceptreject', $sep1); # The various Reject sections are built dynamically based upon a list of reject reply keys, # which are user-configured via $Opts{'reject_reply_patterns'} @RejectPats = (); foreach my $rejpat (split /[ ,]/, $Opts{'reject_reply_patterns'}) { if ($rejpat !~ /^(warn|[45][\d.]{2})$/io) { print STDERR usage "Invalid pattern \"$rejpat\" in reject_reply_patterns"; exit (2); } if (grep (/\Q$rejpat\E/, @RejectPats) == 0) { push @RejectPats, $rejpat } else { print STDERR "Ignoring duplicate pattern \"$rejpat\" in reject_reply_patterns\n"; } } @RejectKeys = @RejectPats; for (@RejectKeys) { s/\./x/g; } print "\tRejectPat: \"@RejectPats\", RejectKeys: \"@RejectKeys\"\n" if $Opts{'debug'} & Logreporters::D_SECT; # Add reject variants foreach my $key (@RejectKeys) { $key = lc($key); my $keyuc = ucfirst($key); my $totalsref = \$Totals{'totalrejects' . $key}; print "\t reject key: $key\n" if $Opts{'debug'} & Logreporters::D_SECT; begin_section_group ($S, 'rejects', "\n"); begin_section_group ($S, 'rejects2', "\n"); add_section ($S, $key . 'rejectrelay', 1, 'd', $keyuc . ' Reject relay denied', $totalsref); add_section ($S, $key . 'rejecthelo', 1, 'd', $keyuc . ' Reject HELO/EHLO', $totalsref); add_section ($S, $key . 'rejectdata', 1, 'd', $keyuc . ' Reject DATA', $totalsref); add_section ($S, $key . 'rejectunknownuser', 1, 'd', $keyuc . ' Reject unknown user', $totalsref); add_section ($S, $key . 'rejectrecip', 1, 'd', $keyuc . ' Reject recipient address', $totalsref); add_section ($S, $key . 'rejectsender', 1, 'd', $keyuc . ' Reject sender address', $totalsref); add_section ($S, $key . 'rejectclient', 1, 'd', $keyuc . ' Reject client host', $totalsref); add_section ($S, $key . 'rejectunknownclient', 1, 'd', $keyuc . ' Reject unknown client host', $totalsref); add_section ($S, $key . 'rejectunknownreverseclient', 1, 'd', $keyuc . ' Reject unknown reverse client host', $totalsref); add_section ($S, $key . 'rejectunverifiedclient', 1, 'd', $keyuc . ' Reject unverified client host', $totalsref); add_section ($S, $key . 'rejectrbl', 1, 'd', $keyuc . ' Reject RBL', $totalsref); add_section ($S, $key . 'rejectheader', 1, 'd', $keyuc . ' Reject header', $totalsref); add_section ($S, $key . 'rejectbody', 1, 'd', $keyuc . ' Reject body', $totalsref); add_section ($S, $key . 'rejectcontent', 1, 'd', $keyuc . ' Reject content', $totalsref); add_section ($S, $key . 'rejectsize', 1, 'd', $keyuc . ' Reject message size', $totalsref); add_section ($S, $key . 'rejectmilter', 1, 'd', $keyuc . ' Reject milter', $totalsref); add_section ($S, $key . 'rejectproxy', 1, 'd', $keyuc . ' Reject proxy', $totalsref); add_section ($S, $key . 'rejectinsufficientspace', 1, 'd', $keyuc . ' Reject insufficient space', $totalsref); add_section ($S, $key . 'rejectconfigerror', 1, 'd', $keyuc . ' Reject server config error', $totalsref); add_section ($S, $key . 'rejectverify', 1, 'd', $keyuc . ' Reject VRFY', $totalsref); add_section ($S, $key . 'rejectetrn', 1, 'd', $keyuc . ' Reject ETRN', $totalsref); add_section ($S, $key . 'rejectlookupfailure', 1, 'd', $keyuc . ' Reject temporary lookup failure', $totalsref); end_section_group ($S, 'rejects2', $sep2); add_section ($S, 'totalrejects' . $key, 0, 'd', "Total $keyuc Rejects", $totalsref); end_section_group ($S, 'rejects', $sep1); $Totals{'totalrejects' . $key} = 0; } begin_section_group ($S, 'byiprejects', "\n"); add_section ($S, 'byiprejects', 1, 'd', 'Reject by IP'); end_section_group ($S, 'byiprejects'); begin_section_group ($S, 'general1', "\n"); add_section ($S, 'connectioninbound', 1, 'd', 'Connections'); add_section ($S, 'connectionlostinbound', 1, 'd', 'Connections lost (inbound)'); add_section ($S, 'connectionlostoutbound', 1, 'd', 'Connections lost (outbound)'); add_section ($S, 'disconnection', 0, 'd', 'Disconnections'); add_section ($S, 'removedfromqueue', 0, 'd', 'Removed from queue'); add_section ($S, 'delivered', 1, 'd', 'Delivered'); add_section ($S, 'sent', 1, 'd', 'Sent via SMTP'); add_section ($S, 'sentlmtp', 1, 'd', 'Sent via LMTP'); add_section ($S, 'forwarded', 1, 'd', 'Forwarded'); add_section ($S, 'resent', 0, 'd', 'Resent'); add_section ($S, 'deferred', 1, 'd', 'Deferred'); add_section ($S, 'deferrals', 1, 'd', 'Deferrals'); add_section ($S, 'bouncelocal', 1, 'd', 'Bounced (local)'); add_section ($S, 'bounceremote', 1, 'd', 'Bounced (remote)'); add_section ($S, 'bouncefailed', 1, 'd', 'Bounce failure'); add_section ($S, 'postscreen', 1, 'd', 'Postscreen'); add_section ($S, 'dnsblog', 1, 'd', 'DNSBL log'); add_section ($S, 'dnsblranks', 1, 'd', 'DNSBL ranks'); add_section ($S, 'envelopesenders', 1, 'd', 'Envelope senders'); add_section ($S, 'envelopesenderdomains', 1, 'd', 'Envelope sender domains'); add_section ($S, 'bcced', 1, 'd', 'BCCed'); add_section ($S, 'filtered', 1, 'd', 'Filtered'); add_section ($S, 'redirected', 1, 'd', 'Redirected'); add_section ($S, 'discarded', 1, 'd', 'Discarded'); add_section ($S, 'prepended', 1, 'd', 'Prepended'); add_section ($S, 'replaced', 1, 'd', 'Replaced'); add_section ($S, 'warned', 1, 'd', 'Warned'); add_section ($S, 'requeued', 0, 'd', 'Requeued messages'); add_section ($S, 'returnedtosender', 1, 'd', 'Expired and returned to sender'); add_section ($S, 'notificationsent', 1, 'd', 'Notifications sent'); add_section ($S, 'policyspf', 1, 'd', 'Policy SPF'); add_section ($S, 'policydweight', 1, 'd', 'Policyd-weight'); add_section ($S, 'postfwd', 1, 'd', 'Postfwd'); add_section ($S, 'postgrey', 1, 'd', 'Postgrey'); end_section_group ($S, 'general1'); begin_section_group ($S, 'general2', "\n"); add_section ($S, 'connecttofailure', 1, 'd', 'Connection failures (outbound)'); add_section ($S, 'timeoutinbound', 1, 'd', 'Timeouts (inbound)'); add_section ($S, 'heloerror', 1, 'd', 'HELO/EHLO conversations errors'); add_section ($S, 'illegaladdrsyntax', 1, 'd', 'Illegal address syntax in SMTP command'); add_section ($S, 'released', 0, 'd', 'Released from hold'); add_section ($S, 'rblerror', 1, 'd', 'RBL lookup errors'); add_section ($S, 'dnserror', 1, 'd', 'DNS lookup errors'); add_section ($S, 'numerichostname', 1, 'd', 'Numeric hostname'); add_section ($S, 'smtpconversationerror', 1, 'd', 'SMTP dialog errors'); add_section ($S, 'hostnameverification', 1, 'd', 'Hostname verification errors (FCRDNS)'); add_section ($S, 'hostnamevalidationerror', 1, 'd', 'Hostname validation errors'); add_section ($S, 'smtpprotocolviolation', 1, 'd', 'SMTP protocol violations'); add_section ($S, 'deliverable', 1, 'd', 'Deliverable (address verification)'); add_section ($S, 'undeliverable', 1, 'd', 'Undeliverable (address verification)'); add_section ($S, 'tablechanged', 0, 'd', 'Restarts due to lookup table change'); add_section ($S, 'pixworkaround', 1, 'd', 'PIX workaround enabled'); add_section ($S, 'tlsserverconnect', 1, 'd', 'TLS connections (server)'); add_section ($S, 'tlsservernocert', 1, 'd', 'TLS no client certificate presented'); add_section ($S, 'tlsclientcertnottrusted', 1, 'd', 'TLS client certificate not trusted'); add_section ($S, 'tlsclientconnect', 1, 'd', 'TLS connections (client)'); add_section ($S, 'saslauth', 1, 'd', 'SASL authenticated messages'); add_section ($S, 'tlsunverified', 1, 'd', 'TLS certificate unverified'); add_section ($S, 'tlsoffered', 1, 'd', 'Host offered TLS'); end_section_group ($S, 'general2'); begin_section_group ($S, 'postfixstate', "\n"); add_section ($S, 'postfixstart', 0, 'd', 'Postfix start'); add_section ($S, 'postfixstop', 0, 'd', 'Postfix stop'); add_section ($S, 'postfixrefresh', 0, 'd', 'Postfix refresh'); add_section ($S, 'postfixwaiting', 0, 'd', 'Postfix waiting to terminate'); end_section_group ($S, 'postfixstate'); begin_section_group ($S, 'backwardscompatible', "\n"); add_section ($S, 'backwardscompatible', 1, 'd', 'Running in backwards compatibile mode'); end_section_group ($S, 'backwardscompatible'); if ($Opts{'debug'} & Logreporters::D_SECT) { print "\tSection table\n"; printf "\t\t%s\n", (ref($_) eq 'HASH' ? $_->{NAME} : $_) foreach @Sections; print "build_sect_table: exit\n" } } # XXX create array of defaults for detail <5, 5-9, >10 sub init_defaults() { map { $Opts{$_} = $Defaults{$_} unless exists $Opts{$_} } keys %Defaults; if (! $Opts{'standalone'}) { # LOGWATCH # these take affect if no env present (eg. nothing in conf file) # 0 to 4 nodelays if ($Opts{'detail'} < 5) { # detail 0 to 4, disable all supplemental reports $Opts{'delays'} = 0; $Opts{'postgrey_delays'} = 0; } } } # XXX ensure something is matched? # XXX cache values so we don't have to substitute X for . each time #match $dsn against list for best fit sub get_reject_key($) { my $reply = shift; my $replyorig = $reply; ($reply) = split / /, $reply; for (my $i = 0; $i <= $#RejectPats; $i++) { #print "TRYING: $RejectPats[$i]\n"; # we'll allow extended DSNs to match (eg. 5.7.1 will match 5..) if ($reply =~ /^$RejectPats[$i]/) { # no /o here, pattern varies #print "MATCHED: orig: $replyorig, reply $reply matched pattern $RejectPats[$i], returning $RejectKeys[$i]\n"; return $RejectKeys[$i]; } } #print "NOT MATCHED: REPLY CODE: '$replyorig', '$reply'\n"; return; } # Replace bare reject limiters with specific reject limiters # based on reject_reply_patterns # sub expand_bare_reject_limiters() { # don't reorder the list of limiters. This breaks --nodetail followed by a # bare reject such as --limit rejectrbl=10. Reordering is no longer necessary # since process_limiters was instituted and using the special __none__ pseudo- # limiter to indicate the position at which --nodetail was found on the command # line. # my ($limiter, @reject_limiters, @non_reject_limiters); my ($limiter, @new_list); # XXX check if limiter matches just one in rejectclasses while ($limiter = shift @Limiters) { if ($limiter =~ /^reject[^_]/) { foreach my $reply_code (@RejectKeys) { printf "bare_reject: \L$reply_code$limiter\n" if $Opts{'debug'} & Logreporters::D_VARS; #push @reject_limiters, lc($reply_code) . $limiter; push @new_list, lc($reply_code) . $limiter; } } elsif ($limiter =~ /^(?:[45]\.\.|Warn)reject[^_]/) { $limiter =~ s/^([45])\.\./$1xx/; #push @reject_limiters, lc $limiter; push @new_list, lc $limiter; } else { #push @non_reject_limiters, $limiter; push @new_list, $limiter; } } #@Limiters = (@reject_limiters, @non_reject_limiters); @Limiters = @new_list; } # Return a usage string, built from: # arg1 + # $usage_str + # a string built from each usable entry in the @Sections table. # reject patterns are special cased to minimize the number of # command line options presented. # sub usage($) { my $ret = ""; $ret = "@_\n" if ($_[0]); $ret .= $usage_str; my ($name, $desc, %reject_types); foreach my $sect (get_usable_sectvars(@Sections, 0)) { if (my ($code,$rej) = ($sect->{NAME} =~ /^(...|warn)(reject.*)$/oi)) { $rej = lc $rej; next if (exists $reject_types{$rej}); $reject_types{$rej}++; $name = '[###]' . $rej; $desc = '###' . substr($sect->{TITLE}, length($code)); } else { $name = lc $sect->{NAME}; $desc = $sect->{TITLE}; } $ret .= sprintf " --%-38s%s\n", "$name" . ' LEVEL', "$desc"; } $ret .= "\n"; return $ret; } 1; # vi: shiftwidth=3 tabstop=3 syntax=perl et logwatch-7.14/scripts/services/syslog-ng0000644000175000017500000006253315075123620020650 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Stefan Jakobs # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################### # This was written and is maintained by: # Stefan Jakobs # # Please send all comments, suggestions, bug reports, # etc, to logwatch at localside.net. ########################################################################### ########################################################################### ## Copyright (c) 2008-2013 Stefan Jakobs ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### #use warnings; use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Version = "1.4-20130219"; my $format = $ENV{'LOGWATCH_FORMAT_TYPE'} || 'other'; # initialize logwatch variables my $ThisLine = ""; my %OtherList = (); # initialize variables which save the stats my ($Starts,$Stops,$Reloads) = ( 0, 0, 0); my ($Perms,$FileOpenErrors) = ( 0, 0); my ($Drops, $WriteErrsSum) = ( 0, 0); my ($ExceedConns) = ( 0); my (%PermFiles, %OpenFiles) = ( (), ()); my (%WriteErrs, %Connections) = ( (), ()); my (%Conns, %ConnsSum) = (); my (%Stats_center, %Stats_source, %Stats_dest) = ( (), (), ()); my (%Stats_dropped, %Stats_supp, %Stats_global) = ( (), (), ()); my (%Stats_program, %Stats_stored, %Stats_net) = ( (), (), ()); my (%Stats_dropped_program, %Stats_supp_program) = ( (), ()); my (%Stats_dropped_net, %Stats_supp_net) = ( (), ()); my (%Warnings, %IntErrors) = ( (), ()); ### Parse the lines ### while (defined($ThisLine = )) { chomp($ThisLine); #TD syslog-ng[2351]: New configuration initialized; if ( ($ThisLine =~ /^[Nn]ew configuration initialized/ ) || ($ThisLine =~ /^Configuration reload finished;/ ) || ($ThisLine =~ /^EOF on control channel, closing connection;/ ) || ($ThisLine =~ /^(?:POLLERR|EOF) occurred while idle;/ ) ) { #ignore } #TD syslog-ng[9754]: Changing permissions on special file /dev/xconsole elsif ($ThisLine =~ /^Changing permissions on special file ((\/[a-zA-Z0-9_]*)*)$/) { %PermFiles = (%PermFiles, $1 => $PermFiles{$1}+1); $Perms++; } #TD syslog-ng[9754]: Cannot open file /tmp/.adir/afile for writing (No such file or directory) elsif ($ThisLine =~ /^Cannot open file ((\/[a-zA-Z0-9_.]*)*) .*/) { # $1 fq file name, $2 only filename %OpenFiles = (%OpenFiles, $1 => $OpenFiles{$1}+1); $FileOpenErrors++; } #TD syslog-ng[9754]: SIGHUP received, restarting syslog-ng #TD syslog-ng[4027]: Configuration reload request received, reloading configuration; elsif ($ThisLine =~ /^SIGHUP received, restarting syslog-ng$/ || $ThisLine =~ /^Configuration reload request received, reloading configuration;/) { $Reloads++; } #TD syslog-ng[9754]: syslog-ng version 1.6.2 starting #TD syslog-ng[3956]: syslog-ng starting up; version='2.0.9' elsif ($ThisLine =~ /^syslog-ng version [\d.]+ starting$/ || $ThisLine =~ /^syslog-ng starting up; version='[\d.]+'$/) { $Starts++; } #TD syslog-ng[9754]: syslog-ng version 1.6.2 going down #TD syslog-ng[20043]: syslog-ng shutting down; version='2.0.9' elsif ($ThisLine =~ /^syslog-ng version [\d.]+ going down$/ || $ThisLine =~ /^syslog-ng shutting down; version='[\d.]+'$/) { $Stops++; } #TD syslog-ng[20043]: Termination requested via signal, terminating; elsif ($ThisLine =~ /^Termination requested via signal, terminating;/) { # happens with shutdown, but it's not for extra accounting } # syslog-ng v1.X #TD syslog-ng[4833]: STATS: dropped 0 elsif ($ThisLine =~ /^STATS: dropped ([0-9]*)$/) { if ($1 != 0) { $Drops = $Drops + $1; } } #TD syslog-ng[4833]: Syslog connection closed; fd='45', client='AF_INET(192.168.1.1:40280)', local='AF_INET(192.168.1.10:625)' #TD syslog-ng[4833]: Syslog connection accepted; fd='52', client='AF_INET(192.168.1.1:40280)', local='AF_INET(192.168.1.10:625)' # syslog-ng v3.X #TD Syslog connection broken; fd='63', server='AF_INET(192.169.1.1:514)', time_reopen='60' : 44 Time(s) #TD Syslog connection established; fd='48', server='AF_INET(192.168.1.1:514)', local='AF_INET(0.0.0.0:0)' elsif ($ThisLine =~ /^Syslog connection (\S+); fd='\d+', (server|client)='AF_INET\(([.\d]+):\d+\)', (?:local='AF_INET\(([.\d]+:\d+)\)'|time_reopen='\d+')?$/) { my $loc = defined($4) ? $4 : '0.0.0.0'; $Connections{"$1 ($2)"}{$loc}{$3}++; } #TD syslog-ng[4833]: Connection broken to AF_INET(XXX.YYY.ZZZ.AAA:BBB), reopening in 60 seconds elsif ($ThisLine =~ /^Connection broken to [A-Z_]*\((([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5})\), reopening in [0-9]* seconds$/) { $Conns{'Connection broken'}{$1}++; $ConnsSum{'Connection broken'}++; } # syslog-ng v2.X #TD syslog-ng[4833]: Connection failed; error='Connection timed out (110)', time_reopen='60' #TD syslog-ng[4833]: Connection failed; error='Connection refused (111)', time_reopen='60' #TD syslog-ng[4833]: Connection broken; time_reopen='60' elsif ($ThisLine =~ /^(Connection \w+); (?:error='([^\(']+) \(\d+\)', )?time_reopen='\d+'/ ) { $Conns{$1}{$2}++; $ConnsSum{$1}++; } #TD syslog-ng[4869]: io.c: do_write: write() failed (errno 111), Connection refused #TD syslog-ng[4869]: I/O error occurred while writing; fd='66', error='Connection refused (111)' elsif ( ($ThisLine =~ /^io\.c: do_write: write\(\) failed \(errno ([\d]+)\)/) or ($ThisLine =~ /I\/O error occurred while writing; fd='\d+', error='[^'(]+ \((\d+)\)'/) ) { $WriteErrs{$1}++; $WriteErrsSum++; } # Log statistics from syslog-ng v2.X #TD syslog-ng[4883]: Log statistics; dropped='program(/path/to/p)=12', # processed='center(queued)=1717', processed='center(received)=916', ... # suppressed='program(/path/to/p)=0' # Log statisctics from syslog-ng v3.X #TD syslog-ng[1625]: Log statistics; processed='destination(newsnotice)=0', # processed='center(queued)=0', processed='src.internal(src#0)=7', # stamp='src.internal(src#0)=1283808150', processed='global(msg_clones)=0', ... elsif ($ThisLine =~ /^Log statistics; /) { my @processed = $ThisLine =~ /processed='([a-z.]*)\((\S*)\)=([0-9]*)'/g; for (my $i=0; $i<@processed; $i=$i+3) { if ($processed[$i] eq "center") { $Stats_center{$processed[$i+1]} = $Stats_center{$processed[$i+1]} + $processed[$i+2]; } elsif ($processed[$i] eq "destination") { $Stats_dest{$processed[$i+1]} = $Stats_dest{$processed[$i+1]} + $processed[$i+2]; } elsif ($processed[$i] eq "source" || $processed[$i] eq "src.internal" || $processed[$i] eq 'src.none' || $processed[$i] eq 'src.journald') { $Stats_source{$processed[$i+1]} = $Stats_source{$processed[$i+1]} + $processed[$i+2]; } elsif ($processed[$i] eq "global") { $Stats_global{$processed[$i+1]} = $Stats_global{$processed[$i+1]} + $processed[$i+2]; } elsif ($processed[$i] eq "dst.program") { $Stats_program{$processed[$i+1]} = $Stats_program{$processed[$i+1]} + $processed[$i+2]; } elsif ($processed[$i] =~ /(?:dst\.)?(?:udp|tcp)/) { $Stats_net{$processed[$i+1]} = $Stats_net{$processed[$i+1]} + $processed[$i+2]; } else { chomp($ThisLine); $OtherList{$ThisLine}++; } } my @dropped = $ThisLine =~ /dropped='([a-z.]*)\((\S*)\)=([0-9]*)'/g; for (my $i=0; $i<@dropped; $i=$i+3) { if ($dropped[$i] eq "program" || $dropped[$i] eq "pipe") { if ($dropped[$i+2] > 0) { $Stats_dropped{$dropped[$i+1]} = $Stats_dropped{$dropped[$i+1]} + $dropped[$i+2]; } } elsif ($dropped[$i] =~ /(?:dst\.)?(?:tcp|udp)/) { if ($dropped[$i+2] > 0) { $Stats_dropped_net{$dropped[$i+1]} = $Stats_dropped_net{$dropped[$i+1]} + $dropped[$i+2]; } } elsif ($dropped[$i] eq "dst.program") { if ($dropped[$i+2] > 0) { $Stats_dropped_program{$dropped[$i+1]} = $Stats_dropped_program{$dropped[$i+1]} + $dropped[$i+2]; } } else { chomp($ThisLine); $OtherList{$ThisLine}++; } } my @suppressed = $ThisLine =~ /suppressed='([a-z.]*)\((\S*)\)=([0-9]*)'/g; for (my $i=0; $i<@suppressed; $i=$i+3) { if ($suppressed[$i] eq "program" || $suppressed[$i] eq "pipe") { if ($suppressed[$i+2] > 0) { $Stats_supp{$suppressed[$i+1]} = $Stats_supp{$suppressed[$i+1]} + $suppressed[$i+2]; } } elsif ($suppressed[$i] =~ /(?:dst\.)?(?:tcp|udp)/) { if ($suppressed[$i+2] > 0) { $Stats_supp_net{$suppressed[$i+1]} = $Stats_supp_net{$suppressed[$i+1]} + $suppressed[$i+2]; } } elsif ($suppressed[$i] eq "dst.program") { if ($suppressed[$i+2] > 0) { $Stats_supp_program{$suppressed[$i+1]} = $Stats_supp_program{$suppressed[$i+1]} + $suppressed[$i+2]; } } else { chomp($ThisLine); $OtherList{$ThisLine}++; } } my @stored = $ThisLine =~ /stored='([a-z.]*)\((\S*)\)=([0-9]*)'/g; for (my $i=0; $i<@stored; $i=$i+3) { if ($stored[$i] =~ "(?:dst\.)?(?:program|tcp|udp)" || $stored[$i] eq "pipe") { if ($stored[$i+2] > 0) { $Stats_stored{$stored[$i+1]} = $Stats_stored{$stored[$i+1]} + $stored[$i+2]; } } else { chomp($ThisLine); $OtherList{$ThisLine}++; } } } # syslog-ng v2.X #TD syslog-ng[1796]: Number of allowed concurrent connections exceeded; num='10', max='10' elsif ($ThisLine =~ /^Number of allowed concurrent connections exceeded/) { $ExceedConns++; } # syslog-ng v3.X #TD syslog-ng[1601]: WARNING: global: the default value of chain_hostnames is changing to # 'no' in version 3.0, please update your configuration accordingly; #TD syslog-ng[1601]: WARNING: you are using the pipe driver, underlying file is not a # FIFO, it should be used by file(); filename='/dev/tty10' elsif ($ThisLine =~ /^WARNING: (.*)$/) { $Warnings{$1}++; } # syslog-ng v3.X #TD syslog-ng[1601]: Configuration file has no version number, assuming ... elsif ($ThisLine =~ /(Configuration file has no version number)/) { $Warnings{$1}++; } # syslog-ng v2.X #TD syslog-ng[1602]: Error initializing new configuration, reverting to old config; elsif ($ThisLine =~ /^Error initializing new configuration, reverting to old config;/) { $IntErrors{"Error initializing new configuration"}{"reverting to old config"}++; } #TD syslog-ng[2550]: Internal error, duplicate configuration elements refer to # the same persistent config; name='afsocket_sd_connections(dgram,AF_INET(0.0.0.0:514))' elsif ($ThisLine =~ /Internal error, ([^;]*); name='([^']*)'/) { $IntErrors{$1}{$2}++; } # syslog-ng v2.X #TD syslog-ng[20709]: Referenced filter rule not found; rule='f_dhcpd' elsif ($ThisLine =~ /(Referenced filter rule not found); (.+)/) { $IntErrors{$1}{$2}++ } # syslog-ng v2.X #TD syslog-ng[2000]: Error in configuration, unresolved filter reference; filter='f_host_dhcp_dns0' elsif ($ThisLine =~ /^Error in configuration, ([^;]+); (.+)/) { $IntErrors{$1}{$2}++; } else { # Report any unmatched entries... chomp($ThisLine); $OtherList{$ThisLine}++; } } sub outCount { my ($msg, $count, $category, $subcat) = @_; if ($format ne 'xml') { print $msg; } else { print "\n"; } } ### generate the output ### if ($format eq 'xml') { # todo, embargo the output until the first actual output.. print "\n"; print "\n"; } if (($Starts || $Stops || $Reloads) && $Detail) { print "\n" if $format eq 'xml'; } if ($Starts && $Detail) { outCount(sprintf("\n%-48s %5i Time(s)", "Syslog-ng started:", $Starts), $Starts, "Service", "started"); } if ($Stops && $Detail) { outCount(sprintf("\n%-48s %5i Time(s)", "Syslog-ng stopped:", $Stops), $Stops, "Service", "stopped"); } if ($Reloads && $Detail) { outCount(sprintf("\n%-48s %5i Time(s)", "Syslog-ng reloaded:", $Reloads), $Reloads, "Service", "reloaded"); } if (($Starts || $Stops || $Reloads) && $Detail) { print "" if $format eq 'xml'; print "\n"; } if ($Perms) { print "\n" if $format eq 'xml'; if ($Detail >= 5) { print "\nSyslog-ng changed the permission on the file(s):" unless $format eq 'xml'; foreach my $file (keys %PermFiles) { outCount(sprintf("\n\t%-41s %5i Time(s)", $file, $PermFiles{$file}), $PermFiles{$file}, 'Perms', $file); } print "\n"; } else { outCount(sprintf("\n%-48s %5i Time(s)\n", "Syslog-ng changed permission on file(s):", $Perms), $Perms, 'Perms'); } print "\n" if $format eq 'xml'; } if ($FileOpenErrors) { print "\n" if $format eq 'xml'; if ($Detail >= 5) { print "\nSyslog-ng could not open the file(s):" unless $format eq 'xml'; foreach my $file (keys %OpenFiles) { outCount(sprintf("\n\t%-41s %5i Time(s)", $file, $OpenFiles{$file}),$OpenFiles{$file},'FileOpenErrors',$file); } print "\n"; } else { outCount(sprintf("\n%-48s %5i Time(s)\n", "Syslog-ng could not open file:", $FileOpenErrors),$FileOpenErrors,'FileOpenErrors'); } print "\n" if $format eq 'xml'; } if (keys %Conns) { print "\n" if $format eq 'xml'; foreach my $cat (keys %Conns) { print "\n" if $format eq 'xml'; if ($Detail >= 5) { print "\n$cat:" unless $format eq 'xml'; foreach my $IP (keys %{$Conns{$cat}}) { outCount(sprintf("\n\t%-41s %5i Time(s)", $IP, $Conns{$cat}{$IP}),$Conns{$cat}{$IP},$cat,$IP); } print "\n"; } else { outCount(sprintf("\n%-48s %5i Time(s)\n", "$cat:", $ConnsSum{$cat}),$ConnsSum{$cat},$cat); } print "\n" if $format eq 'xml'; } print "\n" if $format eq 'xml'; } if (keys %WriteErrs) { print "\n" if $format eq 'xml'; if ($Detail >= 5) { print "\nWrite Error(s):" unless $format eq 'xml'; foreach my $err (keys %WriteErrs) { outCount(sprintf("\n\t%-41s %5i Time(s)", "Error Number $err:", $WriteErrs{$err}),$WriteErrs{$err},'WriteErrors',$err); } print "\n"; } else { outCount(sprintf("\n%-48s %5i Time(s)\n", "Write Error(s):", $WriteErrsSum),$WriteErrsSum,'WriteErrors'); } print "\n" if $format eq 'xml'; } if ($ExceedConns && $Detail >= 5) { print "\n" if $format eq 'xml'; outCount(sprintf("\n%-48s %5i Time(s)\n", "Concurrent Connections Exceeded:", $ExceedConns),$ExceedConns,'ExceedConns'); print "\n" if $format eq 'xml'; } if (keys %Stats_center || keys %Stats_dest || keys %Stats_source || keys %Stats_dropped || keys %Stats_supp || keys %Stats_global || keys %Stats_stored || keys %Stats_program || keys %Stats_net) { my ($lost_rcvd, $lost_dest) = ( 0, 0); print '' if $format eq 'xml'; if ($Stats_center{received} && %Stats_source) { $lost_rcvd = 0 - $Stats_center{received}; map { # skip 'src#X' as this seams to be aggregated into 'src' # skip 'journal' as this is not counted. $lost_rcvd = $lost_rcvd + $Stats_source{$_} unless ($_ =~ /(?:src#\d+|journal)/); } keys %Stats_source; } if ($Stats_center{queued} && %Stats_dest) { $lost_dest = $Stats_center{queued}; map { $lost_dest = $lost_dest - $Stats_dest{$_} } keys %Stats_dest; } if ($Detail >= 6 and $format ne 'xml') { print "\nLog Statistics:"; } if ($lost_rcvd != 0 || $lost_dest != 0) { if ($lost_rcvd != 0) { if ($Detail >= 5) { outCount("\n- Failed to receive $lost_rcvd message(s)!",$lost_rcvd,'Log Statistics','Failed to receive messages'); } } if ($lost_dest != 0) { if ($Detail >= 5 ) { outCount("\n- Failed to save $lost_dest message(s) in logfile(s)!",$lost_dest,'Log Statistics','Failed to save messages'); } else { $Drops = $Drops + $lost_dest; } } if ($Detail >= 5) { print "\n"; } } if ($Detail >= 6) { if (keys %Stats_center) { print "\nCenter:" unless $format eq 'xml'; foreach my $center (sort {$a cmp $b} keys %Stats_center) { outCount(sprintf("\n\t%-34s %12i", $center, $Stats_center{$center}), $Stats_center{$center}, "Center", $center); } } if (keys %Stats_dest) { print "\nDestination:" unless $format eq 'xml'; foreach my $dest (sort {$a cmp $b} keys %Stats_dest) { outCount(sprintf("\n\t%-34s %12i", $dest, $Stats_dest{$dest}), $Stats_dest{$dest}, "Destination", $dest); } } if (keys %Stats_source) { print "\nSource:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_source) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_source{$source}),$Stats_source{$source},"Source",$source); } } if (keys %Stats_net) { print "\nNetwork:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_net) { (my $short_source) = ($source =~ /,?([^,]*)/); outCount(sprintf("\n\t%-34s %12i", $short_source, $Stats_net{$source}),$Stats_net{$source},"Network",$short_source); } } if (keys %Stats_program) { print "\nProgram:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_program) { (my $short_source) = ($source =~ /,?([^,]*)/); outCount(sprintf("\n\t%-34s %12i", $short_source, $Stats_program{$source}),$Stats_program{$source},"Program",$short_source); } } if (keys %Stats_supp) { print "\nSuppressed:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_supp) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_supp{$source}),$Stats_supp{$source},"Suppressed",$source); } } if (keys %Stats_supp_net) { print "\nSuppressed(net):"; foreach my $source (sort {$a cmp $b} keys %Stats_supp_net) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_supp_net{$source}),$Stats_supp_net{$source},"Suppressed(net)",$source); } } if (keys %Stats_supp_program) { print "\nSuppressed(program):" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_supp_program) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_supp_program{$source}),$Stats_supp_program{$source},"Suppressed(program)",$source); } } if (keys %Stats_stored) { print "\nStored:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_stored) { (my $short_source) = ($source =~ /,?([^,]*)/); outCount(sprintf("\n\t%-34s %12i", $short_source, $Stats_stored{$source}),$Stats_stored{$source},"Stored",$short_source); } } if (keys %Stats_global) { print "\nGlobal:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_global) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_global{$source}),$Stats_global{$source},"Global",$source); } } } if (keys %Stats_dropped) { print "\nDropped:" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_dropped) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_dropped{$source}),$Stats_dropped{$source},'Dropped',$source); } } if (keys %Stats_dropped_net) { print "\nDropped(net):" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_dropped_net) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_dropped_net{$source}),$Stats_dropped_net{$source},'Dropped(net)',$source); } } if (keys %Stats_dropped_program) { print "\nDropped(program):" unless $format eq 'xml'; foreach my $source (sort {$a cmp $b} keys %Stats_dropped_program) { outCount(sprintf("\n\t%-34s %12i", $source, $Stats_dropped_program{$source}),$Stats_dropped_program{$source},'Dropped(program)',$source); } } if (keys %Stats_center or keys %Stats_dropped or keys %Stats_dropped_net or keys %Stats_dropped_program) { print "\n"; } print '' if $format eq 'xml'; } if (keys %Connections) { print '' if $format eq 'xml'; print "syslog-ng:" unless $format eq 'xml'; foreach my $state (sort {$a cmp $b} keys %Connections) { my $sum = 0; print "" if $format eq 'xml'; print "\nConnections $state: " unless $format eq 'xml'; print "\n" if $Detail > 2; foreach my $localip (sort {$a cmp $b} keys %{$Connections{$state}}) { print "" if $Detail > 2 and $format eq 'xml'; print " $localip: " if $Detail > 2 and $format ne 'xml'; print "\n" if $Detail > 5; my $sum_pro_ip = 0; foreach my $extip (sort {$a cmp $b} keys %{$Connections{$state}{$localip}}) { outCount("\t$extip : $Connections{$state}{$localip}{$extip} Time(s)\n", $Connections{$state}{$localip}{$extip},'Sum',$extip) if $Detail > 5; $sum = $sum + $Connections{$state}{$localip}{$extip}; $sum_pro_ip = $sum_pro_ip + $Connections{$state}{$localip}{$extip}; } outCount("$sum_pro_ip\n",$sum_pro_ip,'Sum') if $Detail > 2 and $Detail <= 5; print "" if $Detail > 2 and $format eq 'xml'; } outCount("$sum\n",$sum,'Sum') if $Detail <= 2; print '' if $format eq 'xml'; } print '' if $format eq 'xml'; } if ($Drops) { print '' if $format eq 'xml'; outCount("\nSyslog-ng dropped " . $Drops ." line(s)\n",$Drops,'Dropped','lines'); print '' if $format eq 'xml'; } if (keys %IntErrors) { print '' if $format eq 'xml'; print "\nInternal Errors:" unless $format eq 'xml'; foreach my $class (sort {$a cmp $b} keys %IntErrors) { print "\n $class" unless $format eq 'xml'; print "" if $format eq 'xml'; foreach my $error (sort {$a cmp $b} keys %{$IntErrors{$class}}) { outCount(sprintf("\n\t%-41s %5i Time(s)", "$error:", $IntErrors{$class}{$error}),$IntErrors{$class}{$error},'IntErrors',$class); } } print '' if $format eq 'xml'; print "\n"; } if (keys %Warnings) { print '' if $format eq 'xml'; print "\nWarnings:" unless $format eq 'xml'; foreach my $warning (keys %Warnings) { outCount(sprintf("\n\t%-41s %5i Time(s)", "$warning:", $Warnings{$warning}),$Warnings{$warning},'Warning',$warning); } print '' if $format eq 'xml'; print "\n"; } if (keys %OtherList) { print '' if $format eq 'xml'; print "\n**** Unmatched entries ****\n" unless $format eq 'xml'; foreach my $Error (keys %OtherList) { outCount(" $Error : $OtherList{$Error} Time(s)\n",$OtherList{$Error},'UnmatchedEntry',$Error); } print '' if $format eq 'xml'; } if ($format eq 'xml') { # todo, embargo the output until the first actual output.. print "\n"; } ### return without a failure ### exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et logwatch-7.14/scripts/services/yum0000644000175000017500000000571115075123620017533 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2008 Sy Beamont ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $DebugCounter = 0; #Init Array my @OtherList = (); #Init Hashes my ( %PackageUpdated, %PackageInstalled, %PackageDepInstalled, %PackageErased, ); if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside YUM Filter \n\n"; $DebugCounter = 1; } while (defined(my $ThisLine = )) { if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } if ( $ThisLine =~ s/^(?:\(yumex\) )?Updated: ([^ ]+)/$1/ ) { $PackageUpdated{$ThisLine}++; } elsif ( $ThisLine =~ s/^(?:\(yumex\) )?Installed: ([^ ]+)/$1/ ) { $PackageInstalled{$ThisLine}++; } elsif ( $ThisLine =~ s/^(?:\(yumex\) )?Dep Installed: ([^ ]+)/$1/ ) { $PackageDepInstalled{$ThisLine}++; } elsif ( $ThisLine =~ s/^(?:\(yumex\) )?Erased: ([^ ]+)/$1/ ) { $PackageErased{$ThisLine}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } if (keys %PackageInstalled) { print "\nPackages Installed:\n"; foreach my $ThisOne (keys %PackageInstalled) { print " " . $ThisOne; } } if (keys %PackageDepInstalled) { print "\nPackages (Dependency) Installed:\n"; foreach my $ThisOne (keys %PackageDepInstalled) { print " " . $ThisOne; } } if (keys %PackageUpdated) { print "\nPackages Updated:\n"; foreach my $ThisOne (keys %PackageUpdated) { print " ". $ThisOne; } } if (keys %PackageErased) { print "\nPackages Erased:\n"; foreach my $ThisOne (keys %PackageErased) { print " ". $ThisOne; } } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/pam_unix0000644000175000017500000003302615075123620020541 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### ########################################################################## # TO-DO # We really should search for specific strings (authentication failure, # bad username, check pass, password changed, session opened/closed, # account expired, etc., using the service name as a variable in the hash, # instead of having to add a test for every new service. ########################################################################### use strict; use Logwatch ':sort'; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my ($service, $line, %data); while ($line = ) { chomp $line; $service = $line; # for fedora and others if ($line =~ s/^... .. ..:..:.. .+ .+\(pam_unix\)\[\d+\]: //) { $service =~ s/^... .. ..:..:.. .+ (.+)\(pam_unix\)\[\d+\]: .*$/$1/; # new fedora (fc5) version } elsif ( $line =~ s/^... .. ..:..:.. .+ pam_unix\(.+:.+\): // ) { $service =~ s/^... .. ..:..:.. .+ pam_unix\((.+):.+\): .*$/$1/; # fedora with pam_sss } elsif ( $line =~ s/^... .. ..:..:.. .+ pam_sss\(.+:.+\): // ) { if ($line =~ /received for user/) { # ignore this line - paired with authentication failure next; } $service =~ s/^... .. ..:..:.. .+ pam_sss\((.+):.+\): .*$/$1/; # for debian sarge - "normal" lines } elsif ($line =~ s/^... .. ..:..:.. .+ [^ :]+: \(pam_unix\) //) { $service =~ s/^... .. ..:..:.. .+ ([^\s:\[\]]+)(?:\[[0-9]+\]|): \(pam_unix\) .*$/$1/; #for debian sarge - kdm - why can't they log in standard-compliant way? } elsif ( $line =~ s/^... .. ..:..:.. .+ [^\s:\[\]]+: [0-9:\[\]\.]+ \(pam_unix\) //) { $service =~ s/^... .. ..:..:.. .+ ([^\s:\[\]]+): [0-9:\[\]\.]+ \(pam_unix\) .*$/$1/; #for debian woody } elsif ( $line =~ s/^... .. ..:..:.. .+ PAM_unix\[\d+\]: \((.*?)\) // ) { $service =~ s/^... .. ..:..:.. .+ PAM_unix\[\d+\]: \(([^ ]*)\) .*/$1/; # for Ubuntu 7.10 } elsif ( $line =~ s/^... .. ..:..:.. .+ \S+\[\d+\]: pam_unix_\S+\(.+:.+\): // ) { $service =~ s/^... .. ..:..:.. .+ \S+\[\d+\]: pam_unix_\S+\((.+):.+\): .*$/$1/; # for debian and others ? } elsif ($line =~ s/^... .. ..:..:.. \S+ \S+\[\d+\]: PAM //) { $service =~ s/^... .. ..:..:.. \S+ (\S+)\[\d+\]: PAM .*/$1/; } else { next; } # handle password expiring globally if ($line =~ /^password for user (.+) will expire in (\d+) days/) { $data{"all"}{'Password Expiring'}{"$1 in $2 days"}++; next; } # lowercase the service $service = lc($service); # Generic messages if (my ($user) = ($line =~ /^Access denied for user ([^:]+):/)) { $data{$service}{'Access denied'}{$user}++; next; } elsif ($line =~ s/^authentication success; logname=(\S*) uid=(\d+) .*user=(\S*)$/$1($2) -> $3/) { ($Detail >= 5) && $data{$service}{'Authentication Success'}{$line}++; next; } # Service specific messages if ( grep $_ eq $service, qw/ssh sshd login ftp vsftpd nx proftpd rsh remote rlogin rexec systemd-user/) { if ($line =~ s/^session opened for user (.+) by \(uid=\d+\)/$1/) { ($Detail >= 5) && $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ s/^session opened for user ([^ ]*) by ([^ ]*)\(uid=\d+\)/$1 by $2/) { ($Detail >= 5) && $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ s/^session opened for user (.+) by LOGIN\(uid=\d+\)/$1/) { $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ /session closed for user/) { } elsif ($line =~ /^service\(sshd\) ignoring max retries/) { # ignore these lines } elsif ($line =~ s/^authentication failure; .*rhost=(\S*)\s+user=(\S*)$/$2 ($1)/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ s/^authentication failure; .*rhost=(\S*)\s*$/unknown ($1)/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ s/^authentication failure; logname=(\S*) uid=(\d+) .*user=(\S*)$/$1($2) -> $3/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ s/^authentication failure; logname=(\S*) .*rhost=(\S*)\s+user=(\S*)$/($3 or $1)($2): /) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ s/^(\d+) more authentication failures?; .*rhost=(\S*)\s+user=(\S*)$/$3 ($2)/) { $data{$service}{'Authentication Failures'}{$line} += $1; } elsif ($line =~ s/^(\d+) more authentication failures?; .*rhost=(\S*)\s*$/unknown ($2)/) { $data{$service}{'Authentication Failures'}{$line} += $1; } elsif ($line =~ /check pass; user unknown/) { $data{$service}{'Invalid Users'}{'Unknown Account'}++; } elsif ($line =~ s/^password changed for (.+)/$1(by sshd)/) { ($Detail >= 5) && $data{passwd}{'Password changed'}{$line}++; } elsif ($line =~ s/^account (.+) has expired \((?:failed to change password|account expired)\)$/$1/) { $data{$service}{'Expired Accounts'}{$line}++; } elsif ($line =~ s/bad username \[(.*)\]/$1/) { $data{$service}{'Invalid Users'}{"Bad User: $line"}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/su sudo su-l polkit-1/) { if ( my ($logname, $uid, $ruser, $user) = ($line =~ /^authentication failure; logname=(\S*)\s+uid=(\d+) (?:.*ruser=(\S*)\s+)?.*user=(\S*)$/)) { $line = ($logname or $ruser)."($uid) -> $user"; $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ /session closed for user/) { # ignore this line } elsif ($line =~ /conversation failed/) { # ignore this line. An other line will describe the reason. } elsif (my ($nam, $byid) = ($line =~ /session opened for user (.+) by (.+)$/)) { # resolve uid to name if possible my $onam; if ($byid =~ s/^\(uid=(\d+)\)$/$1/) { $onam = getpwuid($byid) || $byid; } elsif ($byid =~ s/^(\S+)\(uid=\d+\)$/$1/) { $onam = $byid; } else { $onam = $byid; } $data{$service}{'Sessions Opened'}{"$onam -> $nam"}++; } elsif ($line =~ s/auth could not identify password for \[(.*)\]/$1/) { $data{$service}{'Not Identify Password For'}{$line}++; } elsif ($line =~ /^account root has password changed in future/) { #I'm not sure whether this info could not be reported } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/passwd propassd/) { if ($line =~ s/^password changed for (.+)/$1/) { ($Detail >= 5) && $data{$service}{'Password changed'}{$line}++; } } elsif (grep $_ eq $service, qw/gdm gdm-password gdm-smartcard gdm-welcome gdm-launch-environment kde kdm kcheckpass xdm imap dovecot cups/) { if ($line =~ s/^session opened for user (.+) by (?:\(unknown\)|\w+)?\(uid=\d+\)/$1/) { ($Detail >= 5) && $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ s/^User info message: (.*)/$1/) { ($Detail >= 8) && $data{$service}{'User Info Message'}{$line}++; } elsif ($line =~ s/^authentication failure;.* user=(.+)$/$1/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ s/^authentication failure;.* ruser=(.+) rhost=.+$/$1/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ /check pass; user unknown/) { $data{$service}{'Invalid Users'}{'Unknown Account'}++; } elsif ($line =~ /session closed for user/) { # ignore this line } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/spop3d pop/) { if ($line =~ s/^session opened for user (.+)/$1/) { $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ /session closed for user/) { # ignore this line } elsif ($line =~ s/^authentication failure; .*user=(.+)$/$1/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ s/^account (.+) has expired (failed to change password)$/$1/) { $data{$service}{'Expired Accounts'}{$line}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif ($service eq 'tpop3d') { if ($line =~ s/^authentication failure; .*rhost=(.+) user=(.+)$/$2 ($1)/) { $data{$service}{'Authentication Failures'}{$line}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/pure-ftpd vsftpd/) { if ($line =~ s/^session opened for user (.+)/$1/) { $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ s/^check pass; (.+)/$1/) { $data{$service}{'Password Failures'}{$line}++; } elsif ($line =~ s/^authentication failure; .*user=(.+)$/$1/) { $data{$service}{'Authentication Failures'}{$line}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/xscreensaver gnome-screensaver kscreensaver cinnamon-screensaver/) { if ($line =~ s/^authentication failure; .*uid=(\d+) euid=(\d+) tty=(.+) ruser= rhost= user=(.+)$/$4($1,$2) on display $3/) { $data{$service}{'Authentication Failures'}{$line}++; } } elsif ($service =~ /^(?:\/[\w\/]+\/|f)?crond?$/ ) { if ($line =~ s/^session opened for user (.+) by \S*\(uid=\d+\)/$1/) { ($Detail >= 5) && $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ /session closed for user/) { # ignore this line } elsif ($line =~ /^account root has password changed in future/) { #I'm not sure whether this info could not be reported } elsif ($line =~ /^adding faulty module: (.+)/) { $data{$service}{'Faulty modules'}{$1}++; } elsif ($line =~ /^unable to dlopen\(.+\): (.+)$/) { $data{$service}{'Unable to dlopen'}{$1}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif ($service eq 'cyrus') { if ($line =~ /check pass; user unknown/) { $data{$service}{'Invalid Users'}{'Unknown Account'}++; } elsif ($line =~ /authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=/) { # ignore this line } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/samba smbd/) { if ($line =~ s/^session opened for user (\S+) by (.+)/$1/) { ($Detail >= 5) && $data{$service}{'Sessions Opened'}{$line}++; } elsif ($line =~ s/^session closed for user (.+)/$1/) { ($Detail >= 8) && $data{$service}{'Sessions Closed'}{$line}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif (grep $_ eq $service, qw/runuser runuser-l/) { if ($line =~/^session (opened)?(\/)?(closed)? for user [\w\.\-]+/) { } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif ($service eq 'atd') { if ($line =~/^session (opened)?(\/)?(closed)? for user [\w\.\-]+/) { } elsif ($line =~ /^account root has password changed in future/) { #I'm not sure whether this info could not be reported } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif ($service eq 'system-config-date') { if ($line =~ s/auth could not identify password for \[(.*)\]/$1/) { $data{$service}{'Not Identify Password For'}{$line}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } elsif ($service eq 'smtp') { if ($line =~ s/^authentication failure; logname=(\S*) uid=(\d+).*user=(\S*)$/$1($2) -> $3/) { $data{$service}{'Authentication Failures'}{$line}++; } elsif ($line =~ /authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=/) { # ignore this line } elsif ($line =~ /check pass; user unknown/) { $data{$service}{'Invalid Users'}{'Unknown Account'}++; } else { $data{$service}{'Unknown Entries'}{$line}++; } } else { $data{$service}{'Unknown Entries'}{$line}++; } } foreach my $service (sort {$a cmp $b} keys %data) { print "$service:\n"; foreach my $type (sort {$a cmp $b} keys %{$data{$service}}) { print " $type:\n"; my $sort = CountOrder(%{$data{$service}{$type}}); foreach my $entry (sort $sort keys %{$data{$service}{$type}}) { print " $entry: $data{$service}{$type}{$entry} Time(s)\n"; } } print "\n"; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/sonicwall0000644000175000017500000006441715075123620020724 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Laurent DUFOUR , # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Laurent DUFOUR , # based on the work of # Kirk Bauer ######################################################## ####################################################### ## Copyright (c) 2008 Laurent DUFOUR ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':all'; use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $DebugCounter = 0; #Init String Containers my ( $Msg, $Temp, $args, $dst_ip, $dst_name, $host_id, $host_sn, $http_result, $interface, $interface_dst, $interface_src, $ip_proto, $number, $op_type, $pad, $port_dst, $port_src, $src_ip, $src_name, $url, $xfer_byte, $xfer_byte_rcvd, $xfer_byte_sent, $xfer_port_type, $xfer_way, ); #Init Array my @OtherList = (); #Init Hashes my ( %AccessRuleAdded, %BadAdminLogins, %BadLogins, %ByteReceived, %ByteSent, %DNSRefreshed, %ICMP_dropped, %IllegalUsers, %Msg, %NTPFailed, %NTPUpdated, %PortPacketReceived, %PortPacketSent, %ProtoPacketReceived, %ProtoPacketSent, %ReloadRequested, %Restarted, %Started, %SysCfgSaved, %SyslogFacility, %SyslogHost, %TCP_NULL_scan, %TCP_dropped, %TotalProtoByteReceived, %TotalProtoByteSent, %UDP_dropped, %URL_GET, %URL_HEAD, %URL_OTHER, %URL_POST, %Users, ); my %configConvert = ( 'human-readable' => 0, 'Human-readable' => 1, 'truncate-readable' => 2, ); # Taken from DiskUsage.pm inside Filesys-DiskUsage-0.02 # # Jose Castro, C<< # Please report any bugs or feature requests to # C, or through the web interface at # L. I will be notified, and then you'll # automatically be notified of progress on your bug as I make changes. # # Copyright 2004 Jose Castro, All Rights Reserved. # #This program is free software; you can redistribute it and/or modify it #under the same terms as Perl itself. # # # convert size to human readable format sub _convert { defined (my $size = shift) || return undef; my $config = {@_}; # $config->{human} || return $size; my $block = $config->{'Human-readable'} ? 1000 : 1024; my @args = qw/B K M G/; while (@args && $size > $block) { shift @args; $size /= $block; } if ($config->{'truncate-readable'} > 0) { $size = sprintf("%.$config->{'truncate-readable'}f",$size); } return "$size$args[0]"; } if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside SONICWALL Filter \n\n"; $DebugCounter = 1; } my ($month,$day,$time,$host_ip,$host,$conn,$msg,$message); while (defined(my $ThisLine = )) { if ( $Debug >= 30 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } ($month,$day,$time,$host_ip,$host_id,$host_sn,$msg)=split(/ +/,$ThisLine,7); if ($ThisLine =~ /sn=/ ) { #mean that we ave to deal with a sonicwall log file line if ( ($ThisLine =~ /traffic/ ) or ($ThisLine =~ /Copyright/ ) or ($ThisLine =~ /removed due to simultaneous rekey/ ) or ($ThisLine =~ /Administrator logged out/ ) or ($ThisLine =~ /Connection (Closed|Opened)/ ) or ($ThisLine =~ /(TCP|UDP) connection dropped/ ) ) { # don't care about this, will code this later } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$pad) = ($ThisLine =~ /msg="UDP packet dropped" n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? (.*)?/) ) { $UDP_dropped{$host_ip}{LookupIP($src_ip)," to ",LookupIP($dst_ip)}++ } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$pad) = ($ThisLine =~ /msg="TCP packet dropped" n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? (.*)?/) ) { $TCP_dropped{$host_ip}{LookupIP($src_ip)," to ",LookupIP($dst_ip)}++ } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$pad) = ($ThisLine =~ /msg="ICMP packet dropped" n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? (.*)?/) ) { $ICMP_dropped{$host_ip}{LookupIP($src_ip)," to ",LookupIP($dst_ip)}++ } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$ip_proto,$xfer_port_type,$op_type,$http_result) = ($ThisLine =~ /n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? proto=(udp|tcp)\/(http|80) op=(HEAD|Other) result=(\d+)/) ) { if ($op_type eq "HEAD") { $URL_HEAD{$host_ip}{$url}++; } else { $URL_OTHER{$host_ip}{$url}++; } } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$ip_proto,$xfer_port_type,$op_type,$xfer_way,$xfer_byte,$http_result,$url,$args) = ($ThisLine =~ /n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? proto=(udp|tcp|icmp)\/(http|80) op=(GET|POST) (rcvd|sent)=(\d+) result=(\d+) dstname=(.*) arg=(.*)(.*)/) ) { if ($op_type eq "GET") { $URL_GET{$host_ip}{$url}++; } else { $URL_POST{$host_ip}{$url}++; } if ($xfer_way eq "sent") { $ProtoPacketSent{$host_ip}{$ip_proto}++; $TotalProtoByteSent{$host_ip}{$ip_proto}=$TotalProtoByteSent{$host_ip}{$ip_proto}+$xfer_byte; if (($ip_proto eq "tcp") or ($ip_proto eq "udp")) { $ByteSent{$host_ip}{$ip_proto,"/",$xfer_port_type}=$ByteSent{$ip_proto}{$ip_proto,"/",$xfer_port_type}+$xfer_byte; $PortPacketSent{$host_ip}{$ip_proto,"/",$xfer_port_type}++; } } else { $ProtoPacketReceived{$host_ip}{$ip_proto}++; $TotalProtoByteReceived{$host_ip}{$ip_proto}=$TotalProtoByteReceived{$host_ip}{$ip_proto}+$xfer_byte; if (($ip_proto eq "tcp") or ($ip_proto eq "udp")) { $ByteReceived{$host_ip}{$ip_proto,"/",$xfer_port_type}=$ByteReceived{$ip_proto}{$ip_proto,"/",$xfer_port_type}+$xfer_byte; $PortPacketReceived{$host_ip}{$ip_proto,"/",$xfer_port_type}++; } } } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$ip_proto,$xfer_port_type,$op_type,$xfer_byte_sent,$xfer_byte_rcvd,$http_result,$url,$args) = ($ThisLine =~ /n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? proto=(udp|tcp|icmp)\/(http|80) op=(GET|POST) sent=(\d+) rcvd=(\d+) result=(\d+) dstname=(.*) (arg=(.*))?/) ) { if ($op_type eq "GET") { $URL_GET{$host_ip}{$url}++; } else { $URL_POST{$host_ip}{$url}++; } $ProtoPacketSent{$host_ip}{$ip_proto}++; $TotalProtoByteSent{$host_ip}{$ip_proto}=$TotalProtoByteSent{$host_ip}{$ip_proto}+$xfer_byte_sent; if (($ip_proto eq "tcp") or ($ip_proto eq "udp")) { $ByteSent{$host_ip}{$ip_proto,"/",$xfer_port_type}=$ByteSent{$ip_proto}{$ip_proto,"/",$xfer_port_type}+$xfer_byte_sent; $PortPacketSent{$host_ip}{$ip_proto,"/",$xfer_port_type}++; } $ProtoPacketReceived{$host_ip}{$ip_proto}++; $TotalProtoByteReceived{$host_ip}{$ip_proto}=$TotalProtoByteReceived{$host_ip}{$ip_proto}+$xfer_byte_rcvd; if (($ip_proto eq "tcp") or ($ip_proto eq "udp")) { $ByteReceived{$host_ip}{$ip_proto,"/",$xfer_port_type}=$ByteReceived{$ip_proto}{$ip_proto,"/",$xfer_port_type}+$xfer_byte_rcvd; $PortPacketReceived{$host_ip}{$ip_proto,"/",$xfer_port_type}++; } } elsif ( ($number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$ip_proto,$xfer_port_type,$xfer_way,$xfer_byte) = ($ThisLine =~ /n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? proto=(udp|tcp|icmp)\/(.*) (rcvd|sent)=(\d+)(.*)/) ) { if ($xfer_way eq "sent") { $ProtoPacketSent{$host_ip}{$ip_proto}++; $TotalProtoByteSent{$host_ip}{$ip_proto}=$TotalProtoByteSent{$host_ip}{$ip_proto}+$xfer_byte; if (($ip_proto eq "tcp") or ($ip_proto eq "udp")) { $ByteSent{$host_ip}{$ip_proto,"/",$xfer_port_type}=$ByteSent{$ip_proto}{$ip_proto,"/",$xfer_port_type}+$xfer_byte; $PortPacketSent{$host_ip}{$ip_proto,"/",$xfer_port_type}++; } } else { $ProtoPacketReceived{$host_ip}{$ip_proto}++; $TotalProtoByteReceived{$host_ip}{$ip_proto}=$TotalProtoByteReceived{$host_ip}{$ip_proto}+$xfer_byte; if (($ip_proto eq "tcp") or ($ip_proto eq "udp")) { $ByteReceived{$host_ip}{$ip_proto,"/",$xfer_port_type}=$ByteReceived{$ip_proto}{$ip_proto,"/",$xfer_port_type}+$xfer_byte; $PortPacketReceived{$host_ip}{$ip_proto,"/",$xfer_port_type}++; } } } #time="2005-03-23 09:03:27" fw=62.2.84.91 pri=5 c=128 m=37 msg="UDP packet dropped" n=3759 src=64.74.133.26:11379:WAN dst=62.2.84.91:33436:WAN^M #Mar 23 12:45:32 10.15.30.1 id=firewall sn=004010144097 time="2005-03-23 11:08:20" fw=62.2.84.91 pri=6 c=1024 m=98 n=61505 src=195.143.213.210:4992:WAN dst=62.2.84.92:1802:DMZ proto=tcp/1802 rcvd=106 ^M elsif ( ($dst_ip,$msg) = ($ThisLine =~ /System Config saved from host (\d+\.\d+\.\d+\.\d+) (.*)/) ) { $SysCfgSaved{$host_ip}{LookupIP($dst_ip)}++; } elsif ( ($dst_ip,$msg) = ($ThisLine =~ /The system configuration was saved from host (\d+\.\d+\.\d+\.\d+) by (.*)/) ) { $SysCfgSaved{$host_ip}{LookupIP($dst_ip)}++; } elsif ( ($ThisLine =~ /Compiled/) ) { $Started{$host_ip}++; } elsif ( ($ThisLine =~ /DNS entries have been automatically refreshed./) ) { $DNSRefreshed{$host_ip}++; } elsif ( ($ThisLine =~ /DNS has been refreshed./) ) { $DNSRefreshed{$host_ip}++; } elsif ( ($ThisLine =~ /Log successfully sent via email/) ) { $SyslogHost{$host_ip}{$host_ip}++; } elsif ( ($ThisLine =~ /Syslog facility has been changed/) ) { $SyslogFacility{$host_ip}++; } elsif ( ($ThisLine =~ /Syslog security facility has been changed/) ) { $SyslogFacility{$host_ip}++; } elsif ( ($ThisLine =~ /The system clock has been updated through NTP./) ) { $NTPUpdated{$host_ip}++; } elsif ( ($ThisLine =~ /failed to get clock through NTP/) ) { $NTPFailed{$host_ip}++; } elsif ( ($ThisLine =~ /Access Rule added/) ) { $AccessRuleAdded{$host_ip}++; } elsif ( ($message) = ($ThisLine =~ /RELOAD: (.*)/) ) { $ReloadRequested{$host_ip}{$message}++; } elsif ( ($message) = ($ThisLine =~ /RESTART: (.*)/) ) { $Restarted{$host_ip}{$message}++; } elsif ( $ThisLine =~ m/msg="Probable TCP NULL scan " n=(\d+) src=(\d+\.\d+\.\d+\.\d+) (.*)/ ) { if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -TCP NULL scan- line\n"; } my $name = LookupIP($2); $Temp = "TCP NULL scan from $name"; $TCP_NULL_scan{$host_ip}{$Temp}++; } elsif ( ($interface) = ($ThisLine =~ /msg="Successful administrator login" n=(\d+) src=(\d+\.\d+\.\d+\.\d+) (.*)/) ) { if ($Debug >= 5) { print STDERR "DEBUG: Found -$1 logged in from $4 using $2\n"; } if ($Detail >= 20) { $Users{$host_ip}{"from ",$2}{"using port 80"}{$1}++; } else { $Users{$host_ip}{"from ",$2}{"using port 80"}{"(all)"}++; } } elsif ( ($interface) = ($ThisLine =~ /msg="(WAN zone administrator login allowed|Web management request allowed)" n=(\d+) usr=(\w+) src=(\d+\.\d+\.\d+\.\d+)(.*)?/) ) { if ($Debug >= 5) { print STDERR "DEBUG: Found -$1 logged in from $5 using $3\n"; } if ($Detail >= 20) { $Users{$host_ip}{"from ",$3}{"using port 80"}{$1}++; } else { $Users{$host_ip}{"from ",$3}{"using port 80"}{"(all)"}++; } } elsif ( $ThisLine =~ m/msg="Administrator login failed - incorrect password" n=(\d+) src=(\d+\.\d+\.\d+\.\d+) (.*)/ ) { if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Failed login- line\n"; } my $name = LookupIP($2); $Temp = "HTTP from $name"; $BadAdminLogins{$host_ip}{"Administrator login failed - incorrect password from $name"}++; $IllegalUsers{$host_ip}{$Temp}++; } elsif ( $ThisLine =~ m/msg="Unknown user attempted to log in" n=(\d+) src=(\d+\.\d+\.\d+\.\d+) dst=(\d+\.\d+\.\d+\.\d+) user=(.*)/ ) { if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Failed login- line\n"; } my $name = LookupIP($2); $Temp = "HTTP from $name"; $BadLogins{$host_ip}{"$4 user attempted to log in from $name"}++; $IllegalUsers{$host_ip}{$Temp}++; } elsif ( $ThisLine =~ m/SSH client at (.+) has attempted to make an SCS connection to interface untrust with IP (.+) but failed (.*)/ ) { my $name = LookupIP($2); $Temp = "SSH from $name"; $BadLogins{$host_ip}{$Temp}++; $IllegalUsers{$host_ip}{$Temp}++; } elsif ( ($Msg,$number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$pad) = ($ThisLine =~ /msg="(Ping of death dropped|Smurf Amplification attack dropped)" n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)?/) ) { $Msg{$host_ip}{$Msg," for ",LookupIP($src_ip)," to ",LookupIP($dst_ip)}++ } elsif ( ($Msg,$number,$src_ip,$port_src,$interface_src,$src_name,$dst_ip,$port_dst,$interface_dst,$dst_name,$pad) = ($ThisLine =~ /msg="(.*)" n=(\d+) src=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)? dst=(\d+\.\d+\.\d+\.\d+):(\d+):(WAN|LAN|DMZ):?(.*)?(S+)?(.*)?/) ) { $Msg{$host_ip}{$Msg," for ",LookupIP($src_ip)," to ",LookupIP($dst_ip)}++ } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } } #end of mean we have a sonic wall logfile line if (keys %Started) { print "\nDevice started :\n"; foreach my $ThisOne (keys %Started) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$Started{$ThisOne}}) { print "\t Started" .$ThatOne . "\t: " . $Started{$ThisOne}{$ThatOne} . "{ Time(s)\n"; } } } if (keys %UDP_dropped) { print "\nDevice where ip UDP packets have been dropped :\n"; foreach my $ThisOne (keys %UDP_dropped) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$UDP_dropped{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $UDP_dropped{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %TCP_dropped) { print "\nDevice where ip TCP packets have been dropped :\n"; foreach my $ThisOne (keys %TCP_dropped) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$TCP_dropped{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $TCP_dropped{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ICMP_dropped) { print "\nDevice where ip ICMP packets have been dropped :\n"; foreach my $ThisOne (keys %ICMP_dropped) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$ICMP_dropped{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ICMP_dropped{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Msg) { print "\nDevice others message :\n"; foreach my $ThisOne (keys %Msg) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$Msg{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Msg{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %URL_GET) ) { print "\nDevice URL GET :\n"; foreach my $ThisOne (keys %URL_GET) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$URL_GET{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $URL_GET{$ThisOne}{$ThatOne} . " times(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %URL_POST) ) { print "\nDevice URL POST :\n"; foreach my $ThisOne (keys %URL_POST) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$URL_POST{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $URL_POST{$ThisOne}{$ThatOne} . " times(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %URL_HEAD) ) { print "\nDevice URL HEAD :\n"; foreach my $ThisOne (keys %URL_HEAD) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$URL_HEAD{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $URL_HEAD{$ThisOne}{$ThatOne} . " times(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %URL_OTHER) ) { print "\nDevice URL OTHER :\n"; foreach my $ThisOne (keys %URL_OTHER) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$URL_OTHER{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $URL_OTHER{$ThisOne}{$ThatOne} . " times(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %ProtoPacketReceived) ) { print "\nDevice Total packets received by protocols :\n"; foreach my $ThisOne (keys %ProtoPacketReceived) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$ProtoPacketReceived{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ProtoPacketReceived{$ThisOne}{$ThatOne} . " packet(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %PortPacketReceived) ) { print "\nDevice Total packets received by ports :\n"; foreach my $ThisOne (keys %PortPacketReceived) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$PortPacketReceived{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $PortPacketReceived{$ThisOne}{$ThatOne} . " packet(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %TotalProtoByteReceived) ) { print "\nDevice Total Bytes received by protocols :\n"; foreach my $ThisOne (keys %TotalProtoByteReceived) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$TotalProtoByteReceived{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . _convert($TotalProtoByteReceived{$ThisOne}{$ThatOne}, %configConvert ) . " Byte(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %ByteReceived) ) { print "\nDevice Total Bytes received by ports :\n"; foreach my $ThisOne (keys %ByteReceived) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$ByteReceived{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . _convert($ByteReceived{$ThisOne}{$ThatOne}, %configConvert ) . " Byte(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %ProtoPacketSent) ) { print "\nDevice Total packets sent by protocols :\n"; foreach my $ThisOne (keys %ProtoPacketSent) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$ProtoPacketSent{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ProtoPacketSent{$ThisOne}{$ThatOne} . " packet(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %PortPacketSent) ) { print "\nDevice Total packets sent by ports :\n"; foreach my $ThisOne (keys %PortPacketSent) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$PortPacketSent{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $PortPacketSent{$ThisOne}{$ThatOne} . " packet(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %TotalProtoByteSent) ) { print "\nDevice Total Bytes sent by protocols :\n"; foreach my $ThisOne (keys %TotalProtoByteSent) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$TotalProtoByteSent{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . _convert($TotalProtoByteSent{$ThisOne}{$ThatOne}, %configConvert ) . " Byte(s)\n"; } } } if ( ( $Detail >= 5 ) and (keys %ByteSent) ) { print "\nDevice Total Bytes sent by ports :\n"; foreach my $ThisOne (keys %ByteSent) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$ByteSent{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . _convert($ByteSent{$ThisOne}{$ThatOne}, %configConvert ) . " Byte(s)\n"; } } } if (keys %NTPUpdated) { print "\nDevice where The system clock has been updated through NTP :\n"; foreach my $ThisOne (keys %NTPUpdated) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$NTPUpdated{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $NTPUpdated{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %NTPFailed) { print "\nDevice where failed to get clock through NTP :\n"; foreach my $ThisOne (keys %NTPFailed) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$NTPFailed{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $NTPFailed{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %DNSRefreshed) { print "\nDevice where DNS have been refreshed :\n"; foreach my $ThisOne (keys %DNSRefreshed) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$DNSRefreshed{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $DNSRefreshed{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SyslogFacility) { print "\nDevice where Syslog facility has been changed :\n"; foreach my $ThisOne (keys %SyslogFacility) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$SyslogFacility{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SyslogFacility{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SyslogHost) { print "\nDevice where Syslog have been mail successfully :\n"; foreach my $ThisOne (keys %SyslogHost) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$SyslogHost{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SyslogHost{$ThisOne}{$ThisOne} . " Time(s)\n"; } } } if (keys %Restarted) { print "\nDevice restarted :\n"; foreach my $ThisOne (keys %Restarted) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$Restarted{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Restarted{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %AccessRuleAdded) { print "\nDevice where rules have been added :\n"; foreach my $ThisOne (keys %AccessRuleAdded) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$AccessRuleAdded{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $AccessRuleAdded{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ReloadRequested) { print "\nDevice reload requested :\n"; foreach my $ThisOne (keys %ReloadRequested) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$ReloadRequested{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ReloadRequested{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SysCfgSaved) { print "\nDevice where system config have been saved :\n"; foreach my $ThisOne (keys %SysCfgSaved) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (keys %{$SysCfgSaved{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SysCfgSaved{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %BadLogins) { print "\nFailed logins from these:\n"; foreach my $ThisOne (keys %BadLogins) { print " " . $ThisOne . ":\n"; for (sort keys %{$BadLogins{$ThisOne}}) { print "\t $_: $BadLogins{$ThisOne}{$_} Time(s)\n"; } } } if (keys %TCP_NULL_scan) { print "\nDevice whcih had been ports scanned :\n"; foreach my $ThisOne (keys %TCP_NULL_scan) { print " " . $ThisOne . ":\n"; for (sort keys %{$TCP_NULL_scan{$ThisOne}}) { print "\t $_: $TCP_NULL_scan{$ThisOne}{$_} Time(s)\n"; } } } if (keys %BadAdminLogins) { print "\nFailed administrator logins from these:\n"; foreach my $ThisOne (keys %BadAdminLogins) { print " " . $ThisOne . ":\n"; for (sort keys %{$BadAdminLogins{$ThisOne}}) { print "\t $_: $BadAdminLogins{$ThisOne}{$_} Time(s)\n"; } } } if (keys %IllegalUsers) { print "\nIllegal users from these:\n"; foreach my $ThisOne (keys %IllegalUsers) { print " " . $ThisOne . ":\n"; for (sort keys %{$IllegalUsers{$ThisOne}}) { print "\t $_: $IllegalUsers{$ThisOne}{$_} Time(s)\n"; } } } if (keys %Users) { print "\nUsers logging in through :\n"; foreach my $ThisOne (keys %Users) { print " " . $ThisOne . ":\n"; foreach my $user (sort {$a cmp $b} keys %{$Users{$ThisOne}}) { print " $user:\n"; my $totalSort = TotalCountOrder(%{$Users{$ThisOne}{$user}}, \&SortIP); foreach my $ip (sort $totalSort keys %{$Users{$ThisOne}{$user}}) { my $name = LookupIP($ip); if ($Detail >= 20) { print " $name:\n"; my $sort = CountOrder(%{$Users{$ThisOne}{$user}{$ip}}); foreach my $method (sort $sort keys %{$Users{$ThisOne}{$user}{$ip}}) { my $val = $Users{$ThisOne}{$user}{$ip}{$method}; my $plural = ($val > 1) ? "s" : ""; print " $method: $val time$plural\n"; } } else { my $val = (values %{$Users{$ThisOne}{$user}{$ip}})[0]; my $plural = ($val > 1) ? "s" : ""; print " $name: $val time$plural\n"; } } } } } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/emerge0000644000175000017500000001177615075123620020175 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Laurent DUFOUR # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Laurent DUFOUR , # based on the work of # Kirk Bauer # # Please send all comments, suggestions, bug reports, # etc, to laurent.dufour@havas.com ######################################################## ######################################################## ## Copyright (c) 2008 Laurent DUFOUR ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; #Init Counters my $DebugCounter = 0; my $EmergeStarted = 0; my $NumberOfPackageInstalled = 0; my $NumberOfPackageUnmerged = 0; #Init Arrays my @OtherList = (); #Init Hashes my ( %EmergeSyncCompleted, %ExitUnsuccessful, %PackageDepInstalled, %PackageInstalled, %PackageUnmerged, %PackageUpdated ); if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside EMERGE Filter \n\n"; $DebugCounter = 1; } while (defined(my $ThisLine = )) { if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } $ThisLine =~ s/^[^ ]* [^ ]* //; if ( ($ThisLine =~ /Finished. Cleaning up/) or ($ThisLine =~ /exiting successfully/) or ($ThisLine =~ /terminating/) or ($ThisLine =~ /Compiling/) or ($ThisLine =~ /=== sync/) or ($ThisLine =~ /Starting rsync with/i) or ($ThisLine =~ /Merging/) or ($ThisLine =~ /Unmerging./) or ($ThisLine =~ /Resuming merge/) or ($ThisLine =~ /AUTOCLEAN/) or ($ThisLine =~ />>> emerge /) or ($ThisLine =~ />>> depclean/) or ($ThisLine =~ /\*\*\* emerge /) or ($ThisLine =~ /Cleaning/) or ($ThisLine =~ /Updating world file/) or ($ThisLine =~ /Post-Build/) or ($ThisLine =~ /Starting retry \d+ of \d+ with/) ) { # Don't care about these... } elsif ( $ThisLine =~ s/emerge on: ([^ ]+) $2/$1 / ) { $EmergeStarted++; } elsif ( $ThisLine =~ s/Sync completed ([^ ]+) $2/$1 / ) { $EmergeSyncCompleted{$ThisLine}++; } elsif ( $ThisLine =~ s/completed emerge ([^ ]+) $2/$1 to / ) { $PackageInstalled{$ThisLine}++; $NumberOfPackageInstalled++; } elsif ( $ThisLine =~ s/unmerge success: $2/$1 to / ) { $PackageUnmerged{$ThisLine}++; $NumberOfPackageUnmerged++; } elsif ( $ThisLine =~ s/^Dep Installed: ([^ ]+)/$1 / ) { $PackageDepInstalled{$ThisLine}++; } elsif ( $ThisLine =~ /exiting unsuccessfully with status \'(\d)\'/ ) { $ExitUnsuccessful{$1}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } if ($EmergeStarted > 0) { print "\nEmerge Started: $EmergeStarted times\n"; } if (keys %EmergeSyncCompleted) { print "\nEmerge Sync Completed:\n"; foreach my $ThisOne (keys %EmergeSyncCompleted) { print " " . $ThisOne; } } if (keys %PackageUnmerged) { print "\nPackage Unmerged: $NumberOfPackageUnmerged times\n"; foreach my $ThisOne (keys %PackageUnmerged) { print " " . $ThisOne; } } if (keys %PackageInstalled) { print "\nPackage Installed: $NumberOfPackageInstalled times\n"; foreach my $ThisOne (keys %PackageInstalled) { print " " . $ThisOne; } } if (keys %PackageDepInstalled) { print "\nPackage Dependency Installed:\n"; foreach my $ThisOne (keys %PackageDepInstalled) { print " " . $ThisOne; } } if (keys %PackageUpdated) { print "\nPackage Updated:\n"; foreach my $ThisOne (keys %PackageUpdated) { print " ". $ThisOne; } } if (keys %ExitUnsuccessful) { print "\nUnsuccessfull exit with:\n"; foreach my $ThisOne (keys %ExitUnsuccessful) { print " Status $ThisOne : $ExitUnsuccessful{$ThisOne} Time(s)\n"; } } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/knockd0000644000175000017500000000505415075123620020172 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # Copyright (c) 2013 Brian Masney ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my %commands; my %stages; my %unknown; while (defined(my $ThisLine = )) { chomp($ThisLine); next if $ThisLine =~ /^starting up, listening on /; next if $ThisLine =~ /OPEN SESAME$/; next if $ThisLine =~ /: sequence timeout /; my ($name, $command) = $ThisLine =~ /^(.*?): running command: (.*)/; if (defined($name)) { $commands{"$name: $command"}++; next; } my ($ip, $name, $stage) = $ThisLine =~ /^(\d+\.\d+\.\d+\.\d+): (.*?): Stage (\d+)/; if (defined ($ip)) { $stages{$name}{$stage}{$ip}++; next; } $unknown{$ThisLine}++; } if (keys %stages) { print "knockd stages reached\n"; foreach my $name (sort keys %stages) { print "\t$name\n"; foreach my $stage (sort keys %{$stages{$name}}) { print "\t\tStage $stage\n"; foreach my $ip (sort keys %{$stages{$name}{$stage}}) { print "\t\t\tFrom $ip: " . $stages{$name}{$stage}{$ip} . " time(s)\n"; } } } } if (keys %commands) { print "\n"; print "knockd commands executed\n"; foreach my $command (sort keys %commands) { print "\t" . $commands{$command} . " time(s): $command\n"; } } if (keys %unknown) { print "\n"; print "knockd unknown log entries\n"; foreach my $line (sort keys %unknown) { print "\t" . $unknown{$line} . " time(s): $line\n"; } } logwatch-7.14/scripts/services/evtsecurity0000644000175000017500000003642415075123620021314 0ustar logwatchlogwatch# Process Windows security events logged to a server, using Snare Agent or # similar. ######################################################## ## Copyright (c) 2008-2014 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use URI::URL; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Ignore_messages = $ENV{'ignore_messages'} || '^$'; my $SuccessAudits = 0; my %SuccessAuditUsers; my %FailureAudits; my %SuccessAudits; my %ClockSkew; my %Errors; my %Information; my %UnknownUser; my %UnknownClient; my %BadPasswords; my %TicketExpired; my %AccessDenied; my %AccountChanged; my %AccountCreated; my %AccountDeleted; my %AccountDisabled; my %AccountEnabled; my %AccountLocked; my %AuditPolicyChanged; my %ExpiredPassword; my %PasswordChanged; my %Logon; my %PrivilegedLogon; my %Logoff; my %WorkstationLocked; my %WorkstationUnlocked; my %OtherList; while (defined(my $ThisLine = )) { # User specified ignore messages, lower cased next if $ThisLine =~ /$Ignore_messages/i; my ($Hostname,$Criticality,$SourceName,$DateTime,$EventID,$SourceName2,$UserName,$SIDType,$EventLogType,$CategoryString,$DataString,$ExpandedString,$Extra); #Determine format if ($ThisLine =~ /MSWinEventLog\[/) { # Snare 4 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$SourceName2,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\[(\d+)\]:(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } elsif ($ThisLine =~ /MSWinEventLog\t/) { # Snare 3 #Parse ($Criticality,$SourceName,$DateTime,$EventID,$SourceName2,$UserName,$SIDType,$EventLogType,$Hostname,$CategoryString,$DataString,$ExpandedString,$Extra) = ($ThisLine =~ /MSWinEventLog\t(\d+)\t(\w+)\t\d+\t([^\t]+)\t(\d+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)\t?([^\t]*)/); } if (!defined($Hostname)) { print STDERR "Cannot parse $ThisLine"; next; } # Modify some items that prevent de-duplication if ($Detail < 10) { $ExpandedString =~ s/Filter Run-Time ID: \d+/Filter Run-Time ID: XXX/; $ExpandedString =~ s/(Key Name:)\s+\{[0-9A-F\-]+\}/$1 {XXX}/g; $ExpandedString =~ s/Logon ID:\s+0x[0-9A-F]+/Logon ID: 0xXXX/; } if ($Detail < 5) { # Reduce ephemeral ports $ExpandedString =~ s/(Client|Destination|Source) Port:(\s+)[3-6]\d{4}/$1 Port:${2}XXXXX/g; $ExpandedString =~ s/Relative Target Name: ([^\t]+)/Relative Target Name: XXX/; } my $url = URI::URL->new("https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=$EventID"); if ($EventID == 4673 or $EventID == 4674) { # An operation was attempted on a privileged object. # These are basically noise # https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4673 # https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4674 # Ignore } elsif ($EventLogType eq "Success Audit") { if ($EventID == 4608 # Windows is starting up. (startups logged by evtsystem) or $EventID == 4688 # A new process has been created. or $EventID == 4689 # A process has exited. ) { # Ignore } elsif ($EventID == 4624 or $EventID == 4648) { $Logon{"$Hostname $UserName"}++ if $Detail >= 5; } elsif ($EventID == 4634 or $EventID == 4647) { $Logoff{"$Hostname $UserName"}++ if $Detail >= 5; } elsif ($EventID == 4672) { $PrivilegedLogon{"$Hostname $UserName"}++ if $Detail > 0; } elsif ($EventID == 4719) { $AuditPolicyChanged{$Hostname}++; } elsif ($EventID == 4720) { $AccountCreated{$UserName}++; } elsif ($EventID == 4722) { $AccountEnabled{$UserName}++; } elsif ($EventID == 4723) { $PasswordChanged{$UserName}++; } elsif ($EventID == 4725) { $AccountDisabled{$UserName}++; } elsif ($EventID == 4726) { $AccountDeleted{$UserName}++; } elsif ($EventID == 4738 or $EventID == 4742) { $AccountChanged{$UserName}++; } elsif ($EventID == 4800) { $WorkstationLocked{"$Hostname $UserName"}++ if $Detail >= 10; } elsif ($EventID == 4801) { $WorkstationUnlocked{"$Hostname $UserName"}++ if $Detail >= 10; } else { $SuccessAudits++; $SuccessAuditUsers{$UserName}++; $SuccessAudits{"$Hostname $ExpandedString\n$url"}++ if $Detail >= 10; } } elsif ($EventLogType eq "Failure Audit") { if ($EventID == 4625) { # An account failed to log on if (my ($account,$domain,$reason) = ($ExpandedString =~ /Account For Which Logon Failed:.*Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Failure Reason:\s+(.+)\s+Status:.*Sub Status:/)) { $FailureAudits{"$Hostname Log On Failure for $domain\\$account: $reason"}++; } elsif (my ($account,$domain,$reason,$process) = ($ExpandedString =~ /Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Failure Reason:\s+(.+)\s+Status:.*Sub Status:.*Caller Process Name:\s+(.*)\s+Network Informaion:/)) { $FailureAudits{"$Hostname Log On Failure for $domain\\$account by $process: $reason"}++; } } elsif (my ($account,$domain,$process) = ($ExpandedString =~ /^A privileged service was called\..*Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Process Name:\s+(.+)\sService/)) { $FailureAudits{"$Hostname Privileged service called for $domain\\$account: $process"}++ if $Detail; } elsif ($EventID == 4768) { # A Kerberos authentication ticket (TGT) was requested my ($Account,$Realm,$Client,$FailureCode) = $ExpandedString =~ /Account Name:\s+(\S*)\s.*Supplied Realm Name:\s+(\S*)\s.*Client Address:\s+(\S+)\s.*Result Code:\s+(\w+)/; if ($FailureCode eq "0x6") { # Client not found in Kerberos database $UnknownClient{"$Account\\$Realm $Client"}++; } elsif ($FailureCode eq "0x12") { $AccountDisabled{"$Account\@$Realm $Client"}++; } elsif ($FailureCode eq "0x17") { # Password has expired $ExpiredPassword{"$UserName"}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4769) { # A Kerberos service ticket was requested my ($Client,$FailureCode) = $ExpandedString =~ /Client Address:\s+(\S+)\s.*Failure Code:\s+(\w+)/; #print STDER "EventID=$EventID Client=$Client FailureCode=$FailureCode ExpandedString=$ExpandedString\n"; if ($FailureCode eq "0x12") { $AccountDisabled{"$Client"}++; } elsif ($FailureCode eq "0x1B") { # KDC_ERR_MUST_USE_USER2USER Server principal valid for user-to-user only # This is an informational response and not an issue } elsif ($FailureCode eq "0x20") { # Ticket expired $TicketExpired{$Client}++; } elsif ($FailureCode eq "0x25") { # Clock skew too great $ClockSkew{$Client}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4771) { # Kerberos pre-authentication failed my ($Account,$Client,$FailureCode) = $ExpandedString =~ /Account Name:\s+(\S+)\s.*Client Address:\s+(\S+)\s.*Failure Code:\s+(\w+)/; if ($FailureCode eq "0x12") { #Clients credentials have been revoked Account disabled, expired, locked out, logon hours. $AccountLocked{"$Account $Client"}++; } elsif ($FailureCode eq "0x18") { #Pre-authentication information was invalid - bad password $BadPasswords{"$Account $Client"}++; } elsif ($FailureCode eq "0x25") { # Clock skew too great $ClockSkew{$Client}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4776) { # The domain controller attempted to validate the credentials for an account my ($Account,$Client,$FailureCode) = $ExpandedString =~ /Logon Account:\s+(\S+)\s+Source Workstation:\s+(\S*)\s.*Error Code:\s+(\w+)/; if (lc($FailureCode) eq "0xc0000064") { # user name does not exist $UnknownUser{"$Account $Client"}++; } elsif (lc($FailureCode) eq "0xc000006a") { # user name is correct but the password is wrong $BadPasswords{"$Account $Client"}++; } elsif (lc($FailureCode) eq "0xc0000071") { # expired password $ExpiredPassword{"$Account $Client"}++; } elsif (lc($FailureCode) eq "0xc0000234") { # account locked $AccountLocked{"$UserName $Client"}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventID == 4957 and $ExpandedString =~ /resolved to an empty set/) { # Windows Firewall did not apply the following rule - because it was not applicable } elsif ($EventID == 6273) { my ($account,$domain,$client) = ($ExpandedString =~ /Account Name:\s+(\S+)\s+Account Domain:\s+(\S+).*Client Friendly Name:\s+(\S+)/); $AccessDenied{"$account\\$domain $client"}++; } else { $FailureAudits{"$Hostname $ExpandedString\n$url"}++; } } elsif ($EventLogType eq "Error") { $ExpandedString =~ s/\s+\d+\s+\d+//; $Errors{"$Hostname $ExpandedString\n$url"}++; } elsif ($EventLogType eq "Information") { next if $ExpandedString =~ /The event logging service has shut down/; next if $Detail < 5; $Information{"$Hostname $ExpandedString\n$url"}++; } else { # Report any unmatched entries... chomp($ThisLine); $OtherList{"Type=$EventLogType $ThisLine"}++; } } if (keys %Errors) { print "\nERRORS:\n"; foreach my $Error (sort keys %Errors) { print " $Error : $Errors{$Error} Times\n"; } } if (keys %ClockSkew) { print "\nClock skew too great\n"; foreach my $Client (sort keys %ClockSkew) { print " $Client : $ClockSkew{$Client} Times\n"; } } if (keys %AccountCreated) { print "\nAccount Created\n"; foreach my $Account (sort keys %AccountCreated) { print " $Account : $AccountCreated{$Account} Times\n"; } } if (keys %AccountDeleted) { print "\nAccount Deleted\n"; foreach my $Account (sort keys %AccountDeleted) { print " $Account : $AccountDeleted{$Account} Times\n"; } } if (keys %AccountDisabled) { print "\nAccount Disabled\n"; foreach my $Account (sort keys %AccountDisabled) { print " $Account : $AccountDisabled{$Account} Times\n"; } } if (keys %AccountEnabled) { print "\nAccount Enabled\n"; foreach my $Account (sort keys %AccountEnabled) { print " $Account : $AccountEnabled{$Account} Times\n"; } } if (keys %AccountChanged) { print "\nAccount Changed\n"; foreach my $Account (sort keys %AccountChanged) { print " $Account : $AccountChanged{$Account} Times\n"; } } if (keys %PasswordChanged) { print "\nPassword Changed\n"; foreach my $Account (sort keys %PasswordChanged) { print " $Account : $PasswordChanged{$Account} Times\n"; } } if (keys %AccountLocked) { print "\nAccount Locked\n"; foreach my $Account (sort keys %AccountLocked) { print " $Account : $AccountLocked{$Account} Times\n"; } } if (keys %ExpiredPassword) { print "\nPassword Expired\n"; foreach my $Account (sort keys %ExpiredPassword) { print " $Account : $ExpiredPassword{$Account} Times\n"; } } if (keys %AccessDenied) { print "\nAccess Denied\n"; foreach my $Item (sort keys %AccessDenied) { print " $Item : $AccessDenied{$Item} Times\n"; } } if (keys %UnknownUser) { print "\nUnknown Users\n"; foreach my $Account (sort keys %UnknownUser) { print " $Account : $UnknownUser{$Account} Times\n"; } } if (keys %UnknownClient) { print "\nUnknown Clients\n"; foreach my $Account (sort keys %UnknownClient) { print " $Account : $UnknownClient{$Account} Times\n"; } } if (keys %BadPasswords) { print "\nBad Passwords\n"; foreach my $Account (sort keys %BadPasswords) { print " $Account : $BadPasswords{$Account} Times\n"; } } if (keys %TicketExpired) { print "\nTicket Expired\n"; foreach my $Client (sort keys %TicketExpired) { print " $Client : $TicketExpired{$Client} Times\n"; } } if (keys %FailureAudits) { print "\nFailure Audits\n"; foreach my $Error (sort keys %FailureAudits) { print " $Error : $FailureAudits{$Error} Times\n"; } } if (keys %AuditPolicyChanged) { print "\nAudit Policy Changed\n"; foreach my $Hostname (sort keys %AuditPolicyChanged) { print " $Hostname : $AuditPolicyChanged{$Hostname} Times\n"; } } # Detail > 0 if (keys %PrivilegedLogon) { print "\nPrivileged Logons\n"; foreach my $User (sort keys %PrivilegedLogon) { print " $User : $PrivilegedLogon{$User} Times\n"; } } # Detail >= 5 if (keys %Logon) { print "\nLogons\n"; foreach my $User (sort keys %Logon) { print " $User : $Logon{$User} Times\n"; } } # Detail >= 5 if (keys %Logoff) { print "\nLogoffs\n"; foreach my $User (sort keys %Logoff) { print " $User : $Logoff{$User} Times\n"; } } # Detail >= 10 if (keys %WorkstationLocked) { print "\nWorkstation Locked\n"; foreach my $User (sort keys %WorkstationLocked) { print " $User : $WorkstationLocked{$User} Times\n"; } } # Detail >= 10 if (keys %WorkstationUnlocked) { print "\nWorkstation Unlocked\n"; foreach my $User (sort keys %WorkstationUnlocked) { print " $User : $WorkstationUnlocked{$User} Times\n"; } } # Detail >= 5 if ($SuccessAudits and ($Detail >= 5) ) { print "\nSuccess Audits " . $SuccessAudits . " Time(s)\n"; foreach my $User (keys %SuccessAuditUsers) { print " $User : $SuccessAuditUsers{$User} Times\n"; } if ($Detail >= 10) { print "\nSuccess Audits\n"; foreach my $Error (sort keys %SuccessAudits) { print " $Error : $SuccessAudits{$Error} Times\n"; } } } if (keys %Information) { print "\nInformational Messages:\n"; foreach my $Item (sort keys %Information) { print " $Item : $Information{$Item} Times\n"; } } if (keys %OtherList) { print "\n**** Unmatched entries ****\n"; foreach my $Error (keys %OtherList) { print " $Error : $OtherList{$Error} Times\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/openvpn0000644000175000017500000004240715075123620020411 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # The openvpn script was written by: # Jim Richardson ######################################################## ##################################################### ## Copyright (c) 2008 Jim Richardson ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'}; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'}; my %Auth; my $Config; my %ConfigValues; my %ConnErrors; my %ConnectCauseDrop; my %Connections; my %Crypt; my %Error; my %IncorrectPassword; my %ManagementCommands; my %ManagementConnections; my $MaxClients; my $MaxConn; my %OtherList; my %PIDERRs; my %PluginCallFailure; my %PluginCallOK; my %VerifyList; my %VersionInfo; my %Warnings; #Init String Containers my ( $Command, $ConfigValue, $Connection, $Err, $PIDERR, $Warning, $algo, $bits, $call, $channel, $cipher, $config, $depth, $dir, $dn, $error, $host, $name, $peer, $plugin, $port, $proto, $status, ); if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG \n\n"; } while (defined(my $ThisLine = )) { chomp($ThisLine); if ($ThisLine =~ /^([\d]+\.[\d]+\.[\d]+\.[\d]+)\:([\d]+) TLS Auth Error: Auth Username\/Password verification failed for peer/) { $IncorrectPassword{$1}++; } # normalise - this could possibly be used for more detailed per host statistics # further down $ThisLine =~ s/^([\d]+\.[\d]+\.[\d]+\.[\d]+)\:([\d]+) //; $ThisLine =~ s/^([\S]+)\/([\d]+\.[\d]+\.[\d]+\.[\d]+)\:([\d]+) //; if ( ($ThisLine =~ /Inactivity timeout \(--ping-restart\), restarting/) or ($ThisLine =~ /^\/sbin\//) or ($ThisLine =~ /^Attempting to establish TCP connection with [\d.]+:\d+/) or ($ThisLine =~ /^Closing TUN\/TAP interface/) or ($ThisLine =~ /^Connection reset, restarting \[\d+\]/) or ($ThisLine =~ /^Control Channel Authentication/) or ($ThisLine =~ /^Control Channel MTU parms/) or ($ThisLine =~ /CRL CHECK OK: \/.*\//) or ($ThisLine =~ /^Data Channel MTU parms/) or ($ThisLine =~ /^Data Channel: using negotiated cipher/) or ($ThisLine =~ /^Delayed exit in \d+ seconds/) or ($ThisLine =~ /^Diffie-Hellman initialized/) or ($ThisLine =~ /^event_wait : Interrupted system call \(code=\d+\)/) or ($ThisLine =~ /^Exiting/) or ($ThisLine =~ /^Expected Remote Options/) or ($ThisLine =~ /^GID set to/) or ($ThisLine =~ /^IFCONFIG POOL/) or ($ThisLine =~ /^ifconfig_pool_read/) or ($ThisLine =~ /^do_ifconfig/) or ($ThisLine =~ /^succeeded -> ifconfig_pool_set/) or ($ThisLine =~ /^IMPORTANT: OpenVPN's default port number is now 1194/) or ($ThisLine =~ /^Initialization Sequence Completed/) or ($ThisLine =~ /^Listening for incoming TCP connection on \S+:\d+/) or ($ThisLine =~ /^LZO compression initializ/) or ($ThisLine =~ /^Local Options (hash|String)/) or ($ThisLine =~ /^MULTI: Learn:/) or ($ThisLine =~ /^MULTI: multi_init called/) or ($ThisLine =~ /^MULTI: multi_create_instance called/) or ($ThisLine =~ /^MULTI: primary virtual IP for/) or ($ThisLine =~ /^MULTI: primary virtual IPv6 for/) or ($ThisLine =~ /^MULTI: TCP INIT maxclients=\d+ maxevents=\d+/) or ($ThisLine =~ /^MULTI: bad source address from client .*, packet dropped/) or ($ThisLine =~ /^MULTI_sva: pool returned IPv4=/) or ($ThisLine =~ /^Need IPv6 code in mroute_extract_addr_from_packet/) or ($ThisLine =~ /^NOTE: UID\/GID downgrade will be delayed because of --client, --pull, or --up-delay/) or ($ThisLine =~ /OpenVPN .* built on [A-Z][a-z]{2} [ 12]?[0-9] [0-9]{4}/) or ($ThisLine =~ /^OPTIONS IMPORT/) or ($ThisLine =~ /^PLUGIN_CALL: plugin function /) or ($ThisLine =~ /^PLUGIN_INIT: POST .* intercepted=/) or ($ThisLine =~ /^Preserving previous TUN\/TAP instance: \w+/) or ($ThisLine =~ /^PUSH: Received control message/) or ($ThisLine =~ /^Re-using pre-shared static key/) or ($ThisLine =~ /^Re-using SSL\/TLS context/) or ($ThisLine =~ /read UDPv4 \[.*\]: No route to host \(code=[0-9]*\)/) or ($ThisLine =~ /^Restart pause, \d+ second\(s\)/) or ($ThisLine =~ /^ROUTE_GATEWAY/) or ($ThisLine =~ /^send_push_reply/) or ($ThisLine =~ /^SENT CONTROL/) or ($ThisLine =~ /^SIGTERM\[hard,[^\]]*\] received, process exiting/) or ($ThisLine =~ /^SIGTERM\[soft,[^\]]*\] received, client-instance exiting/) or ($ThisLine =~ /^SIGUSR1\[soft,(connection-reset|ping-restart)\] received, (process|client-instance) restarting/) or ($ThisLine =~ /Socket Buffers: R=\[[0-9]+->[0-9]+\] S=\[[0-9]+->[0-9]+\]/) or ($ThisLine =~ /^TCP\/UDP: Closing socket/) or ($ThisLine =~ /^TCP\/UDP: Dynamic remote address changed during TCP connection establishment/) or ($ThisLine =~ /^TCP\/UDP: Preserving recently used remote address: /) or ($ThisLine =~ /^TCP connection established with [\d.]+:\d+/) or ($ThisLine =~ /^TCP connection established with \[AF_INET6?\]/) or ($ThisLine =~ /^TCPv\d_(CLIENT|SERVER) link (local|remote)/) or ($ThisLine =~ /^TLS-Auth MTU parms/) or ($ThisLine =~ /^TLS: Initial packet from/) or ($ThisLine =~ /^TLS: soft reset/) or ($ThisLine =~ /^TLS: tls_process: killed expiring key$/) or ($ThisLine =~ /^TLS: move_session: dest=.* src=.* reinit_src=[0-9]*/) or ($ThisLine =~ /^TLS: tls_multi_process: untrusted session promoted to (semi-)?trusted/) or ($ThisLine =~ /TLS: tls_multi_process: killed expiring key/) or ($ThisLine =~ /^TLS: new session incoming connection from .*/) or ($ThisLine =~ /TLS Error: TLS object -> incoming plaintext read error/) or ($ThisLine =~ /TLS ERROR: received control packet with stale session-id=.*/) or ($ThisLine =~ /^TUN\/TAP device \S+ opened/) or ($ThisLine =~ /TUN\/TAP TX queue length set to [0-9]*/) or ($ThisLine =~ /^TUN\/TAP device \S+ exists previously, keep at program end/) or ($ThisLine =~ /^UDPv4 link /) or ($ThisLine =~ /^UID set to/) or ($ThisLine =~ /^VERIFY OK: nsCertType=\w+/) or ($ThisLine =~ /^peer info: /) or ($ThisLine =~ /^chroot to /) or ($ThisLine =~ /^LDAP bind failed: Invalid credentials$/) or ($ThisLine =~ /^MANAGEMENT: Client disconnected$/) or ($ThisLine =~ /^MANAGEMENT: .* listening /) or ($ThisLine =~ /(?:AEAD Decrypt|Authenticate\/Decrypt packet) error: bad packet ID \(may be a replay\): \[ #.* \] -- see the man page entry for --no-replay and --replay-window for more info or silence this warning with --mute-replay-warnings/) ) { # Don't care about these... } elsif (($status, $depth, $dn) = ( $ThisLine =~ /^VERIFY (.*): depth=(.*), (.*)/ )) { #VERIFY OK: depth=0, /C=US/ST=TX/O=Aidant.Enterprises/OU=IT/CN=delta.aidant.net/Email=keymaster@aidant.net: 23 Time(s) $VerifyList{"status: $status depth: $depth DN: $dn"}++; } elsif (($status, $dn) = ( $ThisLine =~ /^VERIFY X509NAME (.*): (.*)/ )) { #VERIFY X509NAME OK: /C=US/ST=TX/O=Aidant.Enterprises/OU=IT/CN=delta.aidant.net/Email=keymaster@aidant.net: 23 Time(s) $VerifyList{"status: $status X509Name DN: $dn"}++; } elsif (($status, $dn) = ( $ThisLine =~ /^CRL CHECK (.*): (.*)/ )) { #CRL CHECK OK: C=US, ST=CO, L=Boulder, O=NWRA, OU=Boulder, CN=user, name=root, emailAddress=xxxx@xxxx.xxx $VerifyList{"CRL check status: $status DN: $dn"}++; } elsif ($ThisLine =~ /^TLS: Username\/Password authentication/) { $VerifyList{$ThisLine}++; } elsif ($ThisLine =~ /^user '.*' authenticated/) { # This will be handled by the above message } elsif ($ThisLine =~ /^Incorrect password supplied for .* "(.*)"/) { $IncorrectPassword{$1}++; } elsif ($ThisLine =~ m/^MULTI: new incoming connection would exceed maximum number of clients/) { $MaxClients++; } elsif ($ThisLine =~ m/^OpenVPN [\d.]+ [\w-]+ [\[\]\w ]+ built on [\w]+ +[\d]+ [\d]+$/) { $VersionInfo{$ThisLine} = 1; } elsif ($ThisLine =~ m/^library versions: /) { $VersionInfo{$ThisLine} = 1; } elsif (($config, $peer, $port) = ($ThisLine =~ m/^\[([\S]+)\] Peer Connection Initiated with [^\d]*([\d]+\.[\d]+\.[\d]+\.[\d]+)\:([\d]+)/)) { push (@{$Connections{$config}{$peer}}, $port) unless grep(/^$port$/,@{$Connections{$config}{$peer}}); } elsif (($peer, $port) = ($ThisLine =~ m/^Peer Connection Initiated with [^\d]*([\d]+\.[\d]+\.[\d]+\.[\d]+)\:([\d]+)/)) { push (@{$Connections{"client"}{$peer}}, $port) unless grep(/^$port$/,@{$Connections{"client"}{$peer}}); } elsif (($dir, $channel, $bits, $algo) = ($ThisLine =~ /^(Incoming|Outgoing) (Control Channel) Authentication: Using ([\d]+ bit) message hash '(\S+)' for HMAC authentication/)) { $Auth{$channel}{$dir}{"$bits $algo"}++; } elsif (($channel, $dir, $bits, $algo) = ($ThisLine =~ /^(Data Channel) (Encrypt|Decrypt): Using ([\d]+ bit) message hash '(\S+)' for HMAC authentication/)) { $Auth{$channel}{$dir}{"$bits $algo"}++; } elsif (($channel, $proto, $cipher) = ($ThisLine =~ /^(Control Channel): ([^,]+), cipher (.+)/)) { $Crypt{$channel}{$proto}{$cipher}++; } elsif (($channel, $dir, $algo, $bits) = ($ThisLine =~ /^(Data Channel) (Encrypt|Decrypt): Cipher '(\S+)' initialized with ([\d]+ bit) key/)) { $Crypt{$channel}{$dir}{"$bits $algo"}++; } elsif (($dir, $channel, $algo, $bits) = ($ThisLine =~ /^(Incoming|Outgoing) (Data Channel): Cipher '(\S+)' initialized with ([\d]+ bit) key/)) { $Crypt{$channel}{$dir}{"$bits $algo"}++; } elsif (($proto, $host, $port, $error) = ($ThisLine =~ /^(TCP|UDP): connect to ([\d.]+):(\d+) failed, will try again in \d+ seconds: (.*)/)) { $ConnErrors{$error}{"$proto $host:$port"}++; } elsif (($proto, $error) = ($ThisLine =~ /^read (\w+)_SERVER \[\]: (.*)/)) { $ConnErrors{$error}{"$proto"}++; } elsif (($name) = ($ThisLine =~ /MULTI: new connection by client '(.*)' will cause previous active sessions by this client to be dropped. Remember to use the --duplicate-cn option if you want multiple clients using the same certificate or username to concurrently connect./)) { $ConnectCauseDrop{$name}++; } elsif (($Command) = ($ThisLine =~ /^MANAGEMENT: CMD '(.*)'/)) { $ManagementCommands{$Command}++; } elsif (($Connection) = ($ThisLine =~ /^MANAGEMENT: Client connected from (.*)/)) { $ManagementConnections{$Connection}++; } elsif ((($Err) = ($ThisLine =~ /(read UDPv4 \[ECONNREFUSED\]: Connection refused \(code=111\))/)) or (($Err) = ($ThisLine =~ /(read UDPv4 \[EHOSTUNREACH\]: No route to host \(code=113\))/))) { $Error{$Err}++; } elsif (($PIDERR) = ($ThisLine =~ /^PID_ERR ([^[]+) \[/)) { $PIDERRs{$PIDERR}++; } elsif (($Warning) = ($ThisLine =~ /^WARNING: (.*)/)) { $Warnings{$Warning}++; } elsif (($plugin,$call,$status) = ($ThisLine =~ /^PLUGIN_CALL: POST (.*)\/(PLUGIN_.*) status=(.*)/)) { if ($status == 0) { $PluginCallOK{$plugin}{$call}++; } else { $PluginCallFailure{$plugin}{$call}++; } } elsif ($ThisLine =~ /(Current Parameter Settings|Connection profiles \[.*\]):$/) { $Config = $1; } elsif ($ThisLine =~ /Connection profiles END$/) { $Config = ""; } elsif (($ConfigValue) = ($ThisLine =~ /^ (.*)/)) { $ConfigValues{$Config}->{$ConfigValue} = 1; } else { # Report any unmatched entries... # remove PID from named messages #$ThisLine =~ s/^(client [.0-9]+)\S+/$1/; $OtherList{$ThisLine}++; } #$LastLine = $ThisLine; } ################################################ if (keys %Warnings) { print "\nWARNINGS:\n"; foreach my $Warning (sort keys %Warnings) { print " " . $Warning . ": " .$Warnings{$Warning}. " Time(s)\n"; } } if (keys %PIDERRs) { print "\nPID_ERRs:\n"; foreach my $PIDERR (sort keys %PIDERRs) { print " " . $PIDERR . ": " .$PIDERRs{$PIDERR}. " Time(s)\n"; } } if(keys %ConnErrors) { print "Connection Errors:\n"; foreach my $error (sort keys %ConnErrors) { print " $error:\n"; foreach my $host (sort keys %{$ConnErrors{$error}}) { print " $host: ".$ConnErrors{$error}{$host}." Time(s)\n"; } } } if (keys %IncorrectPassword) { print "\nIncorrect Password\n"; foreach my $DN (sort {$a cmp $b} keys %IncorrectPassword) { print " $DN: $IncorrectPassword{$DN} Time(s)\n"; } } if (keys %VerifyList) { print "\nVerify\n"; foreach my $line (sort {$a cmp $b} keys %VerifyList) { print " $line: $VerifyList{$line} Time(s)\n"; } } if ($MaxClients) { print "\nMaximum Number of Clients reached $MaxClients Time(s)\n"; } if (keys %Connections) { print "\nConnections:"; foreach my $config (sort keys %Connections) { print "\n Configuration $config:"; foreach my $peer (sort keys %{$Connections{$config}}) { my $ports = $Connections{$config}{$peer}; print "\n $peer connected " . ($#{$ports} + 1) . " Time(s), Ports:"; for (my $i = 0; $i <= $#{$ports}; $i++) { print "\n " if (($i + 16) % 20 == 0); print " $$ports[$i]"; } } print "\n"; } } if (keys %Auth and $Detail >= 10) { print "\nCiphers used for Authentication:"; foreach my $channel (sort keys %Auth) { print "\n $channel:"; foreach my $dir (sort keys %{$Auth{$channel}}) { print "\n $dir:"; foreach my $algo (sort keys %{$Auth{$channel}{$dir}}) { print "\n $algo used $Auth{$channel}{$dir}{$algo} Time(s)"; } } print "\n"; } } if (keys %Crypt and $Detail >= 10) { print "\nCiphers used for Encryption:"; foreach my $channel (sort keys %Crypt) { print "\n $channel:"; foreach my $dir (sort keys %{$Crypt{$channel}}) { print "\n $dir:"; foreach my $algo (sort keys %{$Crypt{$channel}{$dir}}) { print "\n $algo used $Crypt{$channel}{$dir}{$algo} Time(s)"; } } print "\n"; } } if (keys %VersionInfo) { print "\nVersion Information:\n"; foreach my $vers (sort keys %VersionInfo) { print " $vers\n" } } if (keys %ConnectCauseDrop) { print "\n Previous active sessions of the same client dropped upon new connection:\n"; foreach my $name (sort keys %ConnectCauseDrop) { print " client $name: $ConnectCauseDrop{$name} Time(s)\n" } } if (keys %Error) { print "\n UDPv4 errors:\n"; foreach my $Err (sort keys %Error) { print " " . $Err . ": " .$Error{$Err}. " Time(s)\n"; } } if (keys %PluginCallFailure) { print "\nPlugin Call Failures:"; foreach my $plugin (sort keys %PluginCallFailure) { print "\n Plugin $plugin:"; foreach my $call (sort keys %{$PluginCallFailure{$plugin}}) { my $times = $PluginCallFailure{$plugin}{$call}; print "\n $call failed $PluginCallFailure{$plugin}{$call} Time(s)"; } print "\n"; } } if (keys %PluginCallOK and $Detail >= 5) { print "\nPlugin Call OK:"; foreach my $plugin (sort keys %PluginCallOK) { print "\n Plugin $plugin:"; foreach my $call (sort keys %{$PluginCallOK{$plugin}}) { my $times = $PluginCallOK{$plugin}{$call}; print "\n $call succeeded $PluginCallOK{$plugin}{$call} Time(s)"; } print "\n"; } } if (keys %ManagementConnections and $Detail) { print "\nManagement Connections from:\n"; foreach my $Connection (sort keys %ManagementConnections) { print " " . $Connection . ": " .$ManagementConnections{$Connection}. " Time(s)\n"; } } if (keys %ManagementCommands and $Detail) { print "\nManagement Commands:\n"; foreach my $Command (sort keys %ManagementCommands) { print " " . $Command . ": " .$ManagementCommands{$Command}. " Time(s)\n"; } } if (keys %ConfigValues and $Detail >= 10) { foreach my $config (sort keys %ConfigValues) { print "\n$config:"; foreach my $configvalue (sort keys %{$ConfigValues{$config}}) { print "\n $configvalue"; } print "\n"; } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/shaperd0000644000175000017500000001172615075123620020352 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Paweł Gołaszewski # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Paweł Gołaszewski ######################################################## ##################################################### ## Copyright (c) 2008 Paweł Gołaszewski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; ## Initial initialization: my $DelayIncCount = 0; my $DelayDecCount = 0; my $MaxDelay = 0; my $MinDelay = 0; my $TooOldSoft = 0; #Init String Containers my ( $Delay, $Direction, $Ip ); #Init Hashes my ( %BandwidthControl, %DecreasingDelay, %IncreasingDelay, %OtherList, %RemovingClass, ); while (defined(my $ThisLine = )) { chomp($ThisLine); if ( ($ThisLine =~ m/^upload ip: /) or ($ThisLine =~ m/^download ip: /) or ($ThisLine =~ m/^Delay parameter has too low amount for this number of IP's or possibility of server.$/) or ($ThisLine =~ m/^Dlay parameter has too high amount./) or ($ThisLine =~ m/^starting: LAN Traffic shaper/) or ($ThisLine =~ m/^using IP [^ ]* for [^ ]* (local|internet) interface/) or ($ThisLine =~ m/^running, PID=/) or ($ThisLine =~ m/^divide_upload active/) or ($ThisLine =~ m/^Normal termination./) or ($ThisLine =~ m/^Initialisation finished/) or ($ThisLine =~ m/^HTB3 found - quantum option enabled./) or ($ThisLine =~ m/^HTB Upload Class type activated/) or ($ThisLine =~ m/^HTB Download Class type activated/) or ($ThisLine =~ m/^Continuous traffic control enabled/) ) { #ignore these } elsif ( ($Direction,$Ip) = ($ThisLine =~ /^Can't control ([^ ]*) bandwidth of IP ([^ ]*)$/)) { $BandwidthControl{$Direction}{$Ip}++; } elsif ( ($Direction,$Ip) = ( $ThisLine =~ /^Removing ([^ ]*) class of IP ([^ ]*)$/ ) ) { $RemovingClass{$Direction}{$Ip}++; } elsif ( ($Delay) = ( $ThisLine =~ /^Increasing delay to ([0-9]*) seconds$/) ) { $IncreasingDelay{$Delay}++; $DelayIncCount++; } elsif ( ($Delay) = ( $ThisLine =~ /^Decreasing delay to ([0-9]*) seconds$/) ) { $DecreasingDelay{$Delay}++; $DelayDecCount++; } elsif ( $ThisLine =~ m/^It seems that iproute2 didn't work correctly. Please upgrade your iproute2 and\/or kernel./) { $TooOldSoft++; } else { $OtherList{$ThisLine}++; } } ########################### # Print report: if ($TooOldSoft > 0) { print "\nWarning:\n"; print "Too old or broken iproute2/kernel reported $TooOldSoft Time(s)\n"; } if (keys %IncreasingDelay) { foreach my $Delay (keys %IncreasingDelay) { if ($Delay > $MaxDelay) { $MaxDelay = $Delay; } } print "\nDelay increased $DelayIncCount Time(s) up to $MaxDelay seconds.\n"; } if (keys %DecreasingDelay) { foreach my $Delay (keys %DecreasingDelay) { if ($Delay < $MinDelay) { $MinDelay = $Delay; } } print "\nDelay decreased $DelayDecCount Time(s) to $MinDelay seconds.\n"; } if (keys %BandwidthControl) { print "\nCan't control bandwidth:\n"; foreach my $Direction (sort {$a cmp $b} keys %BandwidthControl) { print " $Direction:\n"; foreach my $Ip (sort {$a cmp $b} keys %{$BandwidthControl{$Direction}}) { print " $Ip : $BandwidthControl{$Direction}{$Ip} Time(s)\n"; } } } if (keys %RemovingClass) { print "\nRemoving class for:\n"; foreach my $Direction (sort {$a cmp $b} keys %RemovingClass) { print " $Direction:\n"; foreach my $Ip (sort {$a cmp $b} keys %{$RemovingClass{$Direction}}) { print " $Ip : $RemovingClass{$Direction}{$Ip} Time(s)\n"; } } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print "$line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/cron0000644000175000017500000003440015075123620017657 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2008-2016 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; #Init Counters my $BFMFile= my $INCRONDSS= my $INCRONDStS= my $MailErrors= my $Ntpdate= my $Orphans= my $ntpdatemaxoffset= my $ntpdateminoffset= my $ntpdatenosync= my $Reloads= my $Shutdowns= my $Startups= my $CHUSERHErr= my $PAMAUTHErr= 0; #Init Reusable strings my $Error = ""; my $User = ""; my $FileName = ""; my $Cause = ""; my $Table = ""; #Init Array my @OtherList = (); my $Runs = (); #Init Hashes my ( %ExecutedCommand, %CrontabDeny, %InodeError, %Errors, %BFMFile, %WFO, %SELCONTErr, %UserReloads, %INCRONDSTCr, %INCRONDSTCh, %INCRONDSTDe, %INCRONDUTCr, %INCRONDUTCh, %INCRONDUTDe, %INCRONDErr, %CRONDErr, %CHDIRErr, ); while (defined(my $ThisLine = )) { chomp($ThisLine); if ( ($ThisLine =~ /Updated timestamp for job/) or ($ThisLine =~ /INFO \(pidfile fd = \d+\)/) or ($ThisLine =~ /rsyncd/) or ($ThisLine =~ /INFO \(Running \@(re)?boot jobs\)/) or ($ThisLine =~ /INFO \(Skipping \@(re)?boot jobs -- not system startup\)/) or ($ThisLine =~ /INFO \(not boot nor reboot\)/) or ($ThisLine =~ /INFO \(running with inotify support\)/) or ($ThisLine =~ /INFO \(\@reboot jobs will be run at computer's startup.\)/) or ($ThisLine =~ /INFO \(RANDOM_DELAY will be scaled with factor/) or ($ThisLine =~ /logfile turned over/) or ($ThisLine =~ /ready to process filesystem events/) or # newsyslog on OpenBSD ($ThisLine =~ /loading (system|user) tables/) or ($ThisLine =~ /loading table .*/) or ($ThisLine =~ /void Inotify::Remove\(InotifyWatch\*\): removing watch failed/) or ($ThisLine =~ /error: \(22\) Invalid argument/) or ($ThisLine =~ /pam_unix\(crond:session\): session (?:opened|closed) for user/) or # This is paired with PAM ERROR handled below ($ThisLine =~ /FAILED to authorize user with PAM/) or ($ThisLine =~ /PAM pam_end: NULL pam handle passed/) ) { # Ignore } elsif ( ($ThisLine =~ s/^([^ ]+) \([^ ]+\)\s+//) or ($ThisLine =~ s/^\S+\s+\S+\s+..:..:..\s+\S+\s+\S+\[(\d+)\]:\s+\((\S+)\)\s+//) ) { my $PID = $1; $User = $2; #Not happy with User being everywhere if ($ThisLine =~ s/^CMD \((.+)\)\s*$/$1/) { $Runs->{$User}->{$ThisLine}++; $ExecutedCommand{$PID} = {command=>$ThisLine, user=>$User}; } elsif ($ThisLine =~ /^CMDEND/) { # Ignore - already counted above next; } elsif ($ThisLine =~ s/^CMD FINISH \((.+)\)\s*$/$1/) { $Runs->{$User}->{$ThisLine}++; } elsif ($ThisLine =~ s/^(END|CMD START) \((.+)\)\s*$/$1/) { #Ignore for now, NetBSD users could get tricky with #How many commands started vs finished -mgt } elsif ($ThisLine =~ /ORPHAN \(no passwd entry\)/) { $Orphans++; } elsif ($ThisLine =~ s/^(BEGIN|END) EDIT \((.+)\)\s*$/$2/) { $Runs->{$ThisLine}->{'personal crontab edited'} += 0.5; } elsif ($ThisLine =~ s/^REPLACE \((.+)\)\s*$/$1/) { $Runs->{$ThisLine}->{'personal crontab replaced'}++; } elsif ($ThisLine =~ s/^LIST \((.+)\)\s*$/$1/) { $Runs->{$ThisLine}->{'personal crontab listed'}++; } elsif ($ThisLine =~ s/^DELETE \((.+)\)\s*$/$1/) { $Runs->{$User}->{'personal crontab deleted'}++; } elsif ($ThisLine =~ /^STARTUP \(.*\)\s*$/ ) { $Startups++; } elsif ($ThisLine =~ /^INFO \(Shutting down\)/ ) { $Shutdowns++; } elsif ( $ThisLine =~ /^RELOAD \(.+\)\s*$/ ) { $Runs->{$User}->{'personal crontab reloaded'}++; } elsif ( $ThisLine =~ /^MAIL \(mailed \d+ bytes of output but got status [^ ]+/) { $MailErrors++; } elsif ( $ThisLine =~ /^AUTH \(crontab command not allowed\)/) { $CrontabDeny{$User}++; } elsif ( ($Error) = $ThisLine =~ /^PAM ERROR \((.*)\)/) { $Errors{$User}{$Error}++; } elsif ( $ThisLine =~ /^WRONG INODE INFO \([^ ]+\)/) { $InodeError{$User}++; } elsif ( $ThisLine =~ /session opened/ || $ThisLine =~ /session closed/ ) { # ignore } elsif ( my ($Reason) = ($ThisLine =~ /^error \((.+)\)$/) ) { if ($Reason =~ /^grandchild #(\d+) failed with exit status (\d+)/ && \ $ExecutedCommand{$1}) { # $Reason = $ExecutedCommand{$1}{user}.": command failed with error (".$2."): ".$ExecutedCommand{$1}{command}; $Reason = "failed with error (".$2."): ".$ExecutedCommand{$1}{command}; } $Errors{$ExecutedCommand{$1}{user}}{$Reason}++; } elsif ( ($FileName) = ($ThisLine =~ /BAD FILE MODE \((.+)\)/) ) { $BFMFile{$FileName}++; } elsif ( ($FileName) = ($ThisLine =~ /WRONG FILE OWNER \((.+)\)/) ) { $WFO{$FileName}++; } elsif ($ThisLine =~ /NULL security context for user, but SELinux in permissive mode/ ) { $SELCONTErr{$ThisLine}++; } else { # Report any unmatched entries... push @OtherList, "$ThisLine\n"; } } elsif ( $ThisLine =~ /^RELOAD \(.+\)\s*$/ ) { $Reloads++; } elsif ( ($User) = ($ThisLine =~ /^(.*) \([^ ]+\) RELOAD \(.*\)$/ ) ) { $UserReloads{$User}++; } elsif ( $ThisLine =~ /.*?: Job (.*) started for user ([^ ]*)/) { $Runs->{$2}->{$1}++; } elsif ( ($ThisLine =~ /.*?: Job (.*) (completed|terminated)/) or ($ThisLine =~ /.*?: updating configuration from/) or ($ThisLine =~ /.*?: Exiting with code 0/) or ($ThisLine =~ /.*?: SIGTERM signal received/) ) { # Ignore } elsif ( ($User) = ($ThisLine =~ /.*?: editing ([^ ]*)'s fcrontab.*/)) { $Runs->{$User}->{'-- personal crontab edited'}++; } elsif ( ($User) = ($ThisLine =~ /.*?: listing ([^ ]*)'s fcrontab.*/)) { $Runs->{$User}->{'-- personal crontab listed'}++; } elsif ( ($User) = ($ThisLine =~ /.*?: adding (?:new )?file ([^ ]+)/)) { $Runs->{$User}->{'-- personal crontab updated'}++; $UserReloads{$User}++; } elsif ( $ThisLine =~ /.*?: fcron.* started/) { $Startups++; } elsif ( my ($offset) = ($ThisLine =~ /ntpdate\[\d+\]: adjust time server .* offset (.*) sec/)) { $Ntpdate++; if ( $ntpdateminoffset > $offset ) { $ntpdateminoffset = $offset; } if ( $ntpdatemaxoffset < $offset ) { $ntpdatemaxoffset = $offset; } } elsif ($ThisLine =~ /ntpdate\[\d+\]: no server suitable for synchronization found/) { $ntpdatenosync++; } elsif (($ThisLine =~ /incrond/) && ($ThisLine =~ /starting service/)) { $INCRONDSS++; } elsif (($ThisLine =~ /incrond/) && ($ThisLine =~ /stopping service/)) { $INCRONDStS++; } elsif (($ThisLine =~ /incrond/) && (($Table) = ($ThisLine =~ /system table (.*) created, loading/))) { $INCRONDSTCr{$Table}++; } elsif (($ThisLine =~ /incrond/) && (($User) = ($ThisLine =~ /table for user (.*) created, loading/))) { $INCRONDUTCr{$User}++; } elsif (($ThisLine =~ /incrond/) && (($Table) = ($ThisLine =~ /system table (.*) changed, reloading/))) { $INCRONDSTCh{$Table}++; } elsif (($ThisLine =~ /incrond/) && (($User) = ($ThisLine =~ /table for user (.*) changed, reloading/))) { $INCRONDUTCh{$User}++; } elsif (($ThisLine =~ /incrond/) && (($Table) = ($ThisLine =~ /system table (.*) destroyed, removing/))) { $INCRONDSTDe{$Table}++; } elsif (($ThisLine =~ /incrond/) && (($User) = ($ThisLine =~ /table for user (.*) destroyed, removing/))) { $INCRONDUTDe{$User}++; } elsif ( ($ThisLine =~ /incrond/) && ( (($Error) = ($ThisLine =~ /(cannot create watch for (system table|user) .*: \(2\) No such file or directory)/)) || (($Error) = ($ThisLine =~ /(access denied on (.*) - events will be discarded silently)/)) || (($Error) = ($ThisLine =~ /(unhandled exception occurred)/)) || (($Error) = ($ThisLine =~ /(cannot exec process.*)/)) ) ) { $INCRONDErr{$Error}++; } elsif ( ($ThisLine =~ /crond/) && (($Error) = ($ThisLine =~ /(failed to open PAM security session: (Permission denied|Module is unknown))/)) ) { $CRONDErr{$Error}++; } elsif (( ($Error) = ($ThisLine =~ /ERROR: (failed to change SELinux context)/)) or (($Error) = ($ThisLine =~ /ERROR:(Could not set exec context to .* for .*)/))) { $SELCONTErr{$Error}++; } elsif ($ThisLine =~ /FAILED to authorize user with PAM \(User not known to the underlying authentication module\)/) { $PAMAUTHErr++; } elsif ( ($FileName,$Cause) = ($ThisLine =~ /ERROR chdir failed \((.*)\): (.*)/) ) { $CHDIRErr{"$FileName,$Cause"}++; } elsif ($ThisLine =~ /ERROR \(failed to change user\)/) { $CHUSERHErr++; } else { # Report any unmatched entries... push @OtherList, "$ThisLine\n"; } } ####################################### if (%CrontabDeny) { print "Attempt to use crontab by unauthorized users:\n"; foreach my $User (sort {$a cmp $b} keys %CrontabDeny) { print " $User : $CrontabDeny{$User} Time(s)\n"; } } if (%InodeError) { print "\nInode errors in crontab files of users:\n"; foreach my $User (sort {$a cmp $b} keys %InodeError) { print " $User : $InodeError{$User} Time(s)\n"; } } if (keys %Errors) { print "Errors when running cron:\n"; foreach my $User (sort {$a cmp $b} keys %Errors) { print " User $User:\n"; foreach my $Reason (sort {$a cmp $b} keys %{$Errors{$User}}) { print " $Reason: $Errors{$User}{$Reason} Time(s)\n"; } } } if (%CRONDErr) { printf "\n crond daemon errors \n"; for my $key (keys %CRONDErr) { print " " . $key . ": " . $CRONDErr{$key} . " time(s)\n"; } } if (%INCRONDErr) { printf "\n incrond daemon errors \n"; for my $key (keys %INCRONDErr) { print " " . $key . ": " . $INCRONDErr{$key} . " time(s)\n"; } } if (%SELCONTErr) { printf "\n SELinux context error \n"; for my $key (keys %SELCONTErr) { print " " . $key . ": " . $SELCONTErr{$key} . " time(s)\n"; } } if ($PAMAUTHErr) { printf "\nPAM authentication error: " . $PAMAUTHErr . " time(s)\n"; } if (%CHDIRErr) { printf "\nchdir command failed\n"; foreach (keys %CHDIRErr) { my ($File,$Cause) = split ","; print " for directory " . $File . " (" . $Cause . ")". ": " . $CHDIRErr{"$File,$Cause"} . " time(s)\n"; } } if ($CHUSERHErr) { printf "\nUser change error: " . $CHUSERHErr . " time(s)\n"; } if (keys %{$Runs} and ($Detail >= 5)) { print "\n\nCommands Run:\n"; foreach my $i (sort {$a cmp $b} keys %{$Runs}) { print " User $i:\n"; foreach my $j (sort {$a cmp $b} keys %{$Runs->{$i}}) { print " $j: " . $Runs->{$i}->{$j} . " Time(s)\n"; } } } if ($Orphans) { print " ORPHAN entries: $Orphans\n"; } if ($MailErrors > 0) { print "\nMAIL sending errors $MailErrors Time(s)\n"; } if (keys %BFMFile) { print "\nFiles with bad mode:\n"; foreach my $i (keys %BFMFile) { print " $i\n"; } } if (keys %WFO) { foreach my $i (keys %WFO) { printf "\n Wrong file owner (". $i ."): " . $WFO{$i}. " Time(s)\n"; } } if ($Ntpdate) { print "\nNtpdate: adjusted $Ntpdate times\n"; print "\tMinimum offset $ntpdateminoffset\n"; print "\tMaximum offset $ntpdatemaxoffset\n"; } if($ntpdatenosync) { print "\nNtpDate could not sync: $ntpdatenosync times\n"; } if ($Detail >= 10) { if (keys %UserReloads) { print " User crontabs reloaded:\n"; foreach my $i (keys %UserReloads) { print " $i $UserReloads{$i} Time(s)\n"; } } if ($Startups > 0) { print "\nCRON Started $Startups Time(s)\n"; } if ($Shutdowns > 0) { print "\nCRON Shutdown $Shutdowns Time(s)\n"; } if ($Reloads > 0) { print "\nCRON Reloaded system crontab $Reloads Time(s)\n"; } if ($INCRONDSS) { printf "\n service incrond started " . $INCRONDSS . ": time(s)\n"; } if ($INCRONDStS) { printf "\n service incrond stoped " . $INCRONDStS . ": time(s)\n"; } if ((%INCRONDSTCr) || (%INCRONDUTCr)) { printf "\n created tables \n"; for my $key (keys %INCRONDSTCr) { print " system table " . $key . " created " . $INCRONDSTCr{$key} . ": time(s)\n"; } for my $key (keys %INCRONDUTCr) { print " table for user " . $key . " created " . $INCRONDUTCr{$key}. ": time(s)\n"; } } if ((%INCRONDSTCh) || (%INCRONDUTCh)) { printf "\n changes of tables \n"; for my $key (keys %INCRONDSTCh) { print " system table " . $key . " changed " . $INCRONDSTCh{$key} . ": time(s)\n"; } for my $key (keys %INCRONDUTCh) { print " table for user " . $key . "changed " . $INCRONDUTCh{$key} . ": time(s)\n"; } } if ((%INCRONDSTDe) || (%INCRONDUTDe)) { printf "\n destroyed tables \n"; for my $key (keys %INCRONDSTDe) { print " system table " . $key . " destroyed " . $INCRONDSTDe{$key} . ": time(s)\n"; } for my $key (keys %INCRONDUTDe) { print " table for user ". $key ." destroyed " .$INCRONDUTDe{$key} . ": time(s)\n"; } } } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/rt3140000644000175000017500000001041615075123620017574 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ############################################################################# # rt314: logwatcher processing script for NetGear RT314 router syslog output. # Author: Daniel J. Barrett, dbarrett@blazemonger.com. ############################################################################# ####################################################### ## Copyright (c) 2008 Daniel Barrett ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Socket; use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $separator = "-------------------------------------------------------\n"; ### Partition the data into types my (@portscanlines, @genlines, @otherlines, $begin, $end); my $psl = 0; my $gl = 0; my $ol = 0; while (my $line = ) { $line =~ s/netgear RAS: //; unless ($begin) { $begin = substr($line, 0, 15); } $end = $line; if ( $line =~ /dpo=/ ) { $portscanlines[$psl++] = $line; } elsif ( $line =~ / GEN/ ) { $genlines[$gl++] = $line; } elsif ( $line =~ /last message repeated/ ) { ; } else { $otherlines[$ol++] = $line; } } exit(0) unless ($end); $end = substr($end, 0, 15); ### Print summary if ($Detail >= 10) { print "=== Summary ===\n\n"; } print "Begin:\t$begin\n"; print "End:\t$end\n"; print "\n"; # Extract the port number and source IP address. my @portarray; my %ipaddrs; foreach my $line (@portscanlines) { my $portnum; my $ipaddr; my $dup = $line; $dup =~ s/^.*Src=([0-9.]+) .* dpo=([0-9]*).*$/\1/; $ipaddr = $1; $portnum = $2; $portarray[$portnum]++; if (exists($ipaddrs{$ipaddr})) { $ipaddrs{$ipaddr}++; } else { $ipaddrs{$ipaddr} = 1; } } # Summarize port scans by port number my $total = 0; print "Port #\t\tScans\tService Name\n"; print $separator; for (my $i = 0; $i <= $#portarray; $i++) { if ( $portarray[$i] > 0 ) { print "$i\t\t" . $portarray[$i] . "\t" . getservbyport($i, "tcp") . "\n"; $total += $portarray[$i]; } } print $separator; print "Total\t\t$total\n"; print "\n"; # Summarize port scans by initiating host my @keys = sort {$a <=> $b} (keys %ipaddrs); print "Scanned by\tScans\tHostname Lookup\n"; print $separator; $total = 0; foreach my $ip (@keys) { print "$ip\t" . $ipaddrs{$ip} . "\t" . gethostbyaddr(inet_aton($ip), AF_INET) . "\n"; $total += $ipaddrs{$ip}; } print $separator; print "Total\t\t$total\n"; print "\n"; # Summarize other rule firings if ( $#genlines > 0 ) { print "Rules fired:\t" . $#genlines . "\n"; print "\n"; } # Summarize remaining output if ( $#otherlines > 0 ) { print "Uncategorized:\t" . $#otherlines . "!!!!!!!\n"; print "\n"; } if ($Detail >= 10) { ## Print all data print "=== Raw Data ===\n\n"; if ( $#portscanlines > 0 ) { print "Port scans:\n"; foreach my $line (@portscanlines) { print $line; } print "\n"; } if ( $#genlines > 0 ) { print "Rule lines:\n"; foreach my $line (@genlines) { print $line; } print "\n"; } if ( $#otherlines > 0 ) { print "Other lines:\n"; foreach my $line (@otherlines) { print $line; } print "\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/in.qpopper0000644000175000017500000001237615075123620021021 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Kenneth Porter # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Kenneth Porter # # Please send all comments, suggestions, bug reports, # etc, to shiva@well.com. ######################################################## ##################################################### ## Copyright (c) 2008 Kenneth Porter ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; #Init Hashes my ( %ApopConnections, %Connections, %FailedLogin, %OtherList, %Stats ); while (defined(my $ThisLine = )) { if ( ($ThisLine =~ /xsender/) or ( $ThisLine =~ /.drac.:/ ) or ( $ThisLine =~ /Timing/ ) or ( $ThisLine =~ /-ERR \[AUTH\]/ ) or ( $ThisLine =~ /canonical name of client/ ) or ( $ThisLine =~ /I\/O error flushing output to client/ ) or ( $ThisLine =~ /-ERR SIGHUP or SIGPIPE flagged/ ) or ( $ThisLine =~ /-ERR POP hangup/ ) or ( $ThisLine =~ /-ERR POP EOF or I\/O Error/ ) or ( $ThisLine =~ /-ERR \[IN-USE\] / ) or ( $ThisLine =~ /Incorrect octet count/ ) ) { # We don't care about these } ## Stats: 0 0 0 0 elsif (my ($UserID, $NumDeleted, $BytesDeleted, $NumLeft, $BytesLeft) = ( $ThisLine =~ /Stats: ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)/ )) { $Stats{$UserID}{"Times"}++; $Stats{$UserID}{"NumDel"} += $NumDeleted; $Stats{$UserID}{"BytesDel"} += $BytesDeleted; $Stats{$UserID}{"NumLeft"} = $NumLeft; $Stats{$UserID}{"BytesLeft"} = $BytesLeft; } elsif ( ($UserID) = ($ThisLine =~ /^\[AUTH\] Failed attempted login to ([^ ]+) from host/ )) { $FailedLogin{$UserID}++; } elsif ( $ThisLine =~ s/^connect from ([^ ]+)$/$1/ ) { $Connections{$ThisLine}++; } elsif ( $ThisLine =~ s/^\(v[0-9.]+\) POP login by user "?[^ ]+"? at \([^ ]+\) ([^ ]+)$/$1/ ) { $Connections{$ThisLine}++; } elsif ( $ThisLine =~ s/^apop \"(.*)\".*/$1/ ) { $ApopConnections{$ThisLine}++; } else { # Report any unmatched entries... chomp($ThisLine); $OtherList{$ThisLine}++; } } if ( (keys %Connections) and ($Detail >= 10) ) { print "\nPlaintext Connections:\n"; foreach my $ThisOne (keys %Connections) { print " " . $Connections{$ThisOne} . " from " . $ThisOne; } } if ( (keys %ApopConnections) and ($Detail >= 10) ) { print "\nAPOP Connections:\n"; foreach my $ThisOne (keys %ApopConnections) { print " " . $ApopConnections{$ThisOne} . " from " . $ThisOne; } } if ((keys %Stats) and ($Detail >= 10)) { my ($Times, $NumDel, $BytesDel, $NumLeft, $BytesLeft); print "\nUser Statistics:\n"; print " | Deleted | Kept |\n"; print "User Name Times | Num KBytes | Num KBytes |\n"; foreach my $UserID (sort {$Stats{$b}{"BytesDel"}<=>$Stats{$a}{"BytesDel"}} keys %Stats) { printf("%-15s %5d | %5d %6d | %5d %6d |\n", $UserID, $Stats{$UserID}{"Times"}, $Stats{$UserID}{"NumDel"}, $Stats{$UserID}{"BytesDel"}/1024, $Stats{$UserID}{"NumLeft"}, $Stats{$UserID}{"BytesLeft"}/1024); $Times += $Stats{$UserID}{"Times"}; $NumDel += $Stats{$UserID}{"NumDel"}; $BytesDel += $Stats{$UserID}{"BytesDel"}; $NumLeft += $Stats{$UserID}{"NumLeft"}; $BytesLeft += $Stats{$UserID}{"BytesLeft"}; } print "------------------------+----------------+----------------+\n"; printf("TOTALS %5d | %5d %6d | %5d %6d |\n", $Times, $NumDel, $BytesDel/1024, $NumLeft, $BytesLeft/1024); } if (keys %FailedLogin) { print "\nFailed Logins:\n"; foreach my $UserID (sort {$FailedLogin{$b}<=>$FailedLogin{$a} } keys %FailedLogin) { print " $UserID: $FailedLogin{$UserID} time(s).\n"; }; # foreach }; # if if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$OtherList{$b}<=>$OtherList{$a} } keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/eximstats0000644000175000017500000000422615075123620020742 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # jeff.frost@frostconsultingllc.com # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # This is a wrapper for the eximstats program ######################################################## ######################################################## ## Copyright (c) 2008 Jeff Frost ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':all'; use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $DebugCounter = 0; my $SearchDate = TimeFilter("%Y-%m-%d %H:%M:%S"); if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside eximstats Filter \n\n"; $DebugCounter = 1; } #Should probably verify the ENV variable -mgt open(EXIMSTATS,"| $ENV{'eximstats'}"); while (defined(my $ThisLine = )) { next unless $ThisLine =~ /^$SearchDate /o; if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } print EXIMSTATS $ThisLine; } close EXIMSTATS; exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/denyhosts0000644000175000017500000000324515075123620020741 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my @Denies = (); while (defined(my $ThisLine = )) { chomp($ThisLine); next if ($ThisLine eq ""); if ($ThisLine =~ /\snew denied hosts:\s\[([^\]]+)/i) { my $list = $1; $list =~ s/'//g; push(@Denies, split(",", $list)); } } if (scalar(@Denies)) { print "\n"; print "new denied hosts: \n ".join("\n ", @Denies); print "\n"; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/proftpd-messages0000644000175000017500000002346015075123620022205 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written by Simon Liddington for use with Logwatch ######################################################## ####################################################### ## Copyright (c) 2008 Simon Liddington ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $IgnoreUnmatched = $ENV{'ftpd_ignore_unmatched'}; #Init Counters my $NoEndpoints = 0; my $UnmatchedEntries = 0; #Init String Containers my ( $Email, $Error, $Host, $IP, $Notice, $Option, $Reason, $Temp, $User ); #Init Arrays my @OtherList = (); #Init Hashes my ( %AnonLogins, %BadPasswds, %BadShell, %BadUsers, %ConnectionRefused, %DeletedFiles, %DeprecatedOptions, %ErrorLines, %MaxLoginAttempts, %NoticeLines, %RefusedPorts, %RootLoginAttempt, %TransferTimeout, %UserLogins ); while (defined(my $ThisLine = )) { if ( ( $ThisLine =~ /^FTP session closed/ ) or ( $ThisLine =~ /^(ANONYMOUS )?FTP login as \'.*\' from [^ ]+ \[.*\] to .*/ ) or ( $ThisLine =~ /(PAM|pam_unix)\(.*\): [Aa]uthentication failure/ ) or ( $ThisLine =~ /(PAM|pam_unix)\(.*\): session (opened|closed) for user/ ) or ( $ThisLine =~ /^data_sendfile/ ) or ( $ThisLine =~ /(:| \-) FTP session (closed|opened)/ ) or ( $ThisLine =~ /(:| \-) No certificate files found/ ) or ( $ThisLine =~ /FTP (no transfer|session idle) timeout, disconnected/ ) or ( $ThisLine =~ / masquerading as / ) or ( $ThisLine =~ /mod_delay\// ) or ( $ThisLine =~ /FTP login timed out, disconnected/ ) or ( $ThisLine =~ /Preparing to chroot to directory/ ) or ( $ThisLine =~ /\(.*\[.*\]\)(\:| \-) no such user '.*'/ ) ) { #We don't care about these } elsif ( ($Host,$IP,$Email,) = ( $ThisLine =~ /^FTP session opened: ftp\/ftp (.*)\[(.*)\] (.*)$/ ) ) { $Temp = " " . $Host . " (" . $IP . "): " . $Email . " - "; $AnonLogins{$Temp}++; } elsif ( ($Host, $IP) = ( $ThisLine =~ /\((.*)\[(.*)\]\)(?:\:| \-) ANON .+: Login successful\./ ) ) { $Temp = " " . $Host . " (" . $IP . ")"; $AnonLogins{$Temp}++; } elsif ( ($User,$Host,$IP) = ( $ThisLine =~ /^FTP session opened: (.*\/.*) (.*)\[(.*)\] (.*)$/ ) ) { $Temp = " $Host : $User - "; $UserLogins{$Temp}++; } elsif ( ($Host,$IP,$User) = ( $ThisLine =~ /\((.*)\[(.*)\]\)(?:\:| \-) USER (.+): Login successful/ ) ) { $Temp = " " . $Host . ": " . $User . " - "; $UserLogins{$Temp}++; } elsif ( ($User) = ( $ThisLine =~ /^failed login, can\'t find user \'(.*)\' $/ ) ) { $Temp = " " . "Unknown" . " (" . "Unknown.IP" . "): " . $User . " - "; $BadUsers{$Temp}++; } elsif ( ($User,$Host,$IP) = ( $ThisLine =~ /USER (.*): no such user found from (.*) \[(.*)\] to/ ) ) { #$Temp = " $Host : $User - "; #$BadUsers{$Temp}++; $BadUsers{$User}{$Host}++; # } elsif ( ($Host,$Ip,$User) = ( $ThisLine =~ /\((.*)\[(.*)\]\)(?:\:| \-) no such user '(.*)'/ ) ) { # #$Temp = "$Host($Ip)"; # $BadUsers{$User}{$Host}++; } elsif ( ($Host,$User) = ( $ThisLine =~ /\[(.*)\]\)(?:\:| \-) USER (.*) \(Login failed\): Incorrect password/ ) ) { $Temp = " $Host : $User - "; $BadPasswds{$Temp}++; } elsif ( ($Host,$User) = ( $ThisLine =~ /\[(.*)\]\)(?:\:| \-) USER (.*) \(Login failed\): Invalid shell/ ) ) { $Temp = " $Host : $User - "; $BadShell{$Temp}++; } elsif ( ($Host,$IP) = ( $ThisLine =~ /\((.*)\[(.*)\]\)(?:\:| \-) SECURITY VIOLATION: root login attempted/ ) ) { $RootLoginAttempt{$Host}++; } elsif ( ($Host) = ( $ThisLine =~ /\(((.*)\[(.*)\])\)(?:\:| \-) Maximum login attempts .*exceeded/ ) ) { $MaxLoginAttempts{$Host}++; } elsif ( ($Host,$Reason) = ( $ThisLine =~ /\[(.*)\]\)(?:\:| \-) Refused PORT [\d,]+ \((.*)\)/ ) ) { $Temp = " " . $Host . ": " . $Reason . " - "; $RefusedPorts{$Temp}++; } elsif ( ($Host,$Reason) = ( $ThisLine =~ /\(((.*)\[(.*)\])\)(?:\:| \-) Connection refused \((.*)\)/ ) ) { $ConnectionRefused{$Reason}{$Host}++; } elsif ( ($Host) = ( $ThisLine =~ /\(((.*)\[(.*)\])\)(?:\:| \-) Data transfer stall timeout/ ) ) { $TransferTimeout{$Host}++; } elsif ( $ThisLine =~ m/[^ ]*(?:\:| \-) Fatal: Transport endpoint is not connected/ ) { $NoEndpoints++; } elsif ( ($Option) = ( $ThisLine =~ /warning: (.*) is deprecated/ ) ) { $DeprecatedOptions{$Option}++; } elsif ( ($Host,$IP,$Error) = ( $ThisLine =~ /\((.*)\[(.*)\]\)(?:\:| \-) error: (.*)$/ ) ) { $ErrorLines{$Host}{$Error}++; } elsif ( ($Host,$IP,$Notice) = ( $ThisLine =~ /\((.*)\[(.*)\]\)(?:\:| \-) notice: (.*)$/ ) ) { $NoticeLines{$Host}{$Notice}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } ############################################## if (keys %DeprecatedOptions) { print "\nDeprecated options in config:\n"; foreach my $Option (sort {$a cmp $b} keys %DeprecatedOptions) { print " $Option\n"; } } if ( (keys %AnonLogins) and ($Detail >= 5) ) { print "\nAnonymous FTP Logins:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %AnonLogins) { print $ThisOne . $AnonLogins{$ThisOne} . " Time(s)\n"; } } if ( (keys %DeletedFiles) and ($Detail >= 10) ) { print "\nFiles deleted through FTP:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %DeletedFiles) { print $ThisOne; print @{$DeletedFiles{$ThisOne}}; } } if (keys %UserLogins) { print "\nUser FTP Logins:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %UserLogins) { print $ThisOne . $UserLogins{$ThisOne} . " Time(s)\n"; } } if (keys %RootLoginAttempt) { print "\nSECURITY VIOLATION!!!!\n"; print "Root login attempt from:\n"; foreach my $Host (sort {$a cmp $b} keys %RootLoginAttempt) { print " $Host : $RootLoginAttempt{$Host} Time(s)\n"; } } if (keys %MaxLoginAttempts) { print "\nMaximum login attempts exceeded from hosts:\n"; foreach my $Host (sort {$a cmp $b} keys %MaxLoginAttempts) { print " $Host : $MaxLoginAttempts{$Host} Time(s)\n"; } } if (keys %ConnectionRefused) { print "\nConnection refused with reason:\n"; foreach my $Reason (sort {$a cmp $b} keys %ConnectionRefused) { print " $Reason :\n"; foreach my $Host (sort {$a cmp $b} keys %{$ConnectionRefused{$Reason}}) { print " $Host : $ConnectionRefused{$Reason}{$Host} Time(s)\n"; } } } if ( ( (keys %BadUsers) or (keys %BadPasswds) ) and ($Detail >= 5) ) { print "\nFailed FTP Logins:\n"; if ( (keys %BadUsers) and ($Detail >= 5) ) { print "\n Invalid Username:\n"; foreach my $User (sort {$a cmp $b} keys %BadUsers) { print " $User:\n"; foreach my $Host (sort {$a cmp $b} keys %{$BadUsers{$User}}) { print " $Host : $BadUsers{$User}{$Host} Time(s)\n"; } } } if ( (keys %BadPasswds) and ($Detail >= 5) ) { print "\n Incorrect Password:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %BadPasswds) { print $ThisOne . $BadPasswds{$ThisOne} . " Time(s)\n"; } } if ( (keys %BadShell) and ($Detail >= 5) ) { print "\n Invalid Shell:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %BadShell) { print $ThisOne . $BadShell{$ThisOne} . " Time(s)\n"; } } } if ( (keys %RefusedPorts) and ($Detail >= 5) ) { print "\nRefused PORTs:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %RefusedPorts) { print $ThisOne . $RefusedPorts{$ThisOne} . " Time(s)\n"; } } if ( (keys %TransferTimeout) and ($Detail >= 5) ) { print "\nData transfer stall timeout:\n"; foreach my $Host (sort {$a cmp $b} keys %TransferTimeout) { print " $Host : $TransferTimeout{$Host} Time(s)\n"; } } if ( (keys %NoticeLines) and ($Detail >= 10) ) { print "\nNotices Reported by Host:\n"; foreach my $Host (sort {$a cmp $b} keys %NoticeLines) { print " $Host:\n"; foreach my $Notice (sort {$a cmp $b} keys %{$NoticeLines{$Host}}) { print " $Notice : $NoticeLines{$Host}{$Notice} Time(s)\n"; } } } if ( (keys %ErrorLines) and ($Detail >= 5) ) { print "\nErrors Reported by Host:\n"; foreach my $Host (sort {$a cmp $b} keys %ErrorLines) { print " $Host:\n"; foreach my $Errors (sort {$a cmp $b} keys %{$ErrorLines{$Host}}) { print " $Errors : $ErrorLines{$Host}{$Errors} Time(s)\n"; } } } if ($NoEndpoints > 0) { print "\nTransport endpoint is not connected error $NoEndpoints Time(s)\n"; } if (($#OtherList >= 0) and (not $IgnoreUnmatched)) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/sshd0000644000175000017500000011457715122642254017677 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use Logwatch ':all'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $IgnoreHost = $ENV{'sshd_ignore_host'} || ""; my $RefusedConnectionsThreshold = $ENV{'refused_connections_threshold'} || 0; my $IllegalUsersThreshold = $ENV{'illegal_users_threshold'} || 0; DoLookup( $ENV{'sshd_ip_lookup'} ); my $DebugCounter = 0; #Init String Containers my ( $Action, $Address, $Addresses, $BytesRead, $BytesWritten, $ClientVer, $Code, $Dir, $EmptyUser, $Error, $File, $FingerP, $Flags, $From, $Host, $IP, $IlegUser, $InvaUser, $Key, $Line, $Method, $Mode, $Modtime, $Offer, $Option, $Perm, $Pom, $Pom1, $Pom2, $Port, $Prio, $Reason, $Received, $Sent, $To, $User, $Why, $realm, $user, $hr, $min, $sec, $conn, ); # No sense in running if 'sshd' doesn't even exist on this system... #unless (( -f "/usr/sbin/sshd" ) or ( -f "/usr/local/sbin/sshd") or ( -f "/usr/lib/ssh/sshd")) { # exit (0); #} my %Users = (); my %IllegalUsers = (); my %TooManyFailures = (); my %NoIdent = (); my %BindFailed = (); my %BadLogins = (); my %NoRevMap = (); my %RefusedConnections = (); my %RefusedAuthentication = (); my %NegotiationFailed = (); my %DisconnectReceived = (); my %RootLogin = (); my %PamReleaseFail = (); my %PamError = (); my %PamChroot = (); my %PamDeny = (); my %ShadowInfo = (); my %TTYModesFail = (); my %LoginLock = (); my %PostPonedAuth = (); my %LockedAccount = (); my %AllowUsers = (); my %DenyUsers = (); my %AllowGroups = (); my %DenyGroups = (); my %NoGroups = (); my %NoShellUsers = (); my %ShellNotExecutableUsers = (); my %DeprecatedOption = (); my %MisMatch = (); my %KrbAutFail = (); my %KrbAutErr = (); my %KrbErr = (); my @Scanned = (); my %OtherList = (); my %ChmodErr = (); my %ChownErr = (); my %Krb_realm = (); my %ConnectFailed = (); my %Chroot = (); my %CloseDir = (); my %CloseFileReadWrite = (); my %OpenDir = (); my %OpenFile = (); my %RealPath = (); my %Session = (); my %SetModtime = (); my %Stat = (); my %ClientVers = (); my $sftpRequests = 0; my $NetworkErrors = 0; my $Kills = 0; my $Starts = 0; my $StatusNoSuchFile = 0; my $BytesSent = 0; my $BytesReceived = 0; my $NoCipher = 0; my $MaxStartupsThrottling = 0; my $MaxStartupsDrops = 0; my $MaxStartupsTime = 0; my %MaxStartupsIPs = (); my %DroppedConnections = (); my $multiline = 0; if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside SSHD Filter \n\n"; $DebugCounter = 1; } while (defined(my $ThisLine = )) { if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } chomp($ThisLine); if ( $multiline ) { if ( ($Host) = ($ThisLine =~ /^banner exchange: Connection from ([^ ]+)(?: port \d+)?:/) ) { $NegotiationFailed{$Reason}{$Host}{$Offer}++; } elsif ( ($Host) = ($ThisLine =~ /^Connection reset by ([^ ]+)(?: port \d+)?/) ) { $NegotiationFailed{$Reason}{$Host}{$Offer}++; } elsif ( ($Host) = ($ThisLine =~ /^Connection closed by authenticating user [^ ]+ ([^ ]+)(?: port \d+)?/) ) { $NegotiationFailed{$Reason}{$Host}{$Offer}++; } } $multiline = 0; if ( ($ThisLine =~ /^pam_succeed_if: requirement "uid < 100" (not|was) met by user /) or ($ThisLine =~ /^pam_succeed_if\(.*?\): requirement "uid >= 1000" (not|was) met by user /) or ($ThisLine =~ m/^(log: )?$/ ) or ($ThisLine =~ m/^(log: )?\^\[\[60G/ ) or ($ThisLine =~ m/^(log: )? succeeded$/ ) or ($ThisLine =~ m/^(log: )?Closing connection to/) or ($ThisLine =~ m/^(log: )?Starting sshd:/ ) or ($ThisLine =~ m/^(log: )?sshd \-TERM succeeded/ ) or ($ThisLine =~ m/^Bad protocol version identification .*:? [\d.]+/ ) or ($ThisLine =~ m/^Bad protocol version identification.*Big-Brother-Monitor/ ) or ($ThisLine =~ m/^Connection closed by/) or ($ThisLine =~ m/^Disconnecting: Command terminated on signal \d+/) or ($ThisLine =~ m/^Disconnecting: server_input_channel_req: unknown channel -?\d+/) or ($ThisLine =~ m/^connect from \d+\.\d+\.\d+\.\d+/) or ($ThisLine =~ m/^(fatal: )?Timeout before authentication/ ) or ($ThisLine =~ m/^fatal: no hostkey alg/) or ($ThisLine =~ m/Connection from .* port /) or ($ThisLine =~ m/Postponed (keyboard-interactive|publickey) for [^ ]+ from [^ ]+/) or ($ThisLine =~ m/Read from socket failed/) or ($ThisLine =~ m/sshd startup\s+succeeded/) or ($ThisLine =~ m/sshd shutdown\s+succeeded/) or ($ThisLine =~ m/^Found matching [DR]SA key: /) or ($ThisLine =~ m/^error: key_read: type mismatch: encoding error/) or ($ThisLine =~ m/^channel_lookup: -?\d+: bad id/) or ($ThisLine =~ m/^error: channel \d+: chan_read_failed for istate/) or # Result of setting PermitRootLogin to forced-commands-only ($ThisLine =~ m/^Root login accepted for forced command\.( \[preauth\])?$/) or # usually followed by a session opened for user ($ThisLine =~ m/^pam_krb5\[\d+\]: authentication succeeds for /) or ($ThisLine =~ m/^nss_ldap: reconnect/) or ($ThisLine =~ /gkr-pam: gnome-keyring-daemon started properly/) or ($ThisLine =~ /gkr-pam: unable to locate daemon control file/) or ($ThisLine =~ m/^pam_ldap: error trying to bind as user "[^"]+" \(Invalid credentials\)/) or ($ThisLine =~ m/^pam_ldap: ldap_starttls_s: Can't contact LDAP server/) or ($ThisLine =~ m/^pam_sss\(sshd:.*\)/) or ($ThisLine =~ m/^\(pam_unix\) .*/) or ($ThisLine =~ m/^pam_unix\(.*:.*\)/) or ($ThisLine =~ m/^pam_unix_auth:/) or ($ThisLine =~ m/^pam_sepermit\(.*:.*\)/) or ($ThisLine =~ /pam_krb5: authentication succeeds for `([^ ]*)'/) or ($ThisLine =~ /pam_succeed_if\(.*:.*\): error retrieving information about user [a-zA-Z]*/ ) or ($ThisLine =~ /pam_winbind\(sshd:account\): user .* granted access/) or ($ThisLine =~ /pam_winbind\(sshd:account\): user .* OK/) or ($ThisLine =~ /pam_systemd\(sshd:session\): Moving/) or ($ThisLine =~ /pam_systemd\(sshd:session\): .*: Connection reset by peer/) or ($ThisLine =~ /userauth_finish: .*: Connection reset by peer/) or ($ThisLine =~ /PAM \d+ more authentication failures?;/) or ($ThisLine =~ /^PAM service\(sshd\) ignoring max retries;/) or ($ThisLine =~ /^Failed keyboard-interactive for from/ ) or ($ThisLine =~ /^Keyboard-interactive \(PAM\) userauth failed/ ) or ($ThisLine =~ /^debug1: /) or ($ThisLine =~ /Set \/proc\/self\/oom_(score_)?adj (from -?\d )?to -?\d/ ) or ($ThisLine =~ /Starting session: (forced-command|subsystem|shell|command)/ ) or ($ThisLine =~ /Found matching \w+ key:/ ) or ($ThisLine =~ /User child is on pid \d/ ) or ($ThisLine =~ /Nasty PTR record .* is set up for [\da-fA-F.:]+(?:%\S+)?, ignoring/) or ($ThisLine =~ /Exiting on signal / ) or ($ThisLine =~ /Disconnected from [\da-fA-F.:]*(?:%\S+)? port \d*/ ) or ($ThisLine =~ /Disconnected from user \S+ [\da-fA-F.:]*(?:%\S+)? port \d*/ ) or ($ThisLine =~ /Disconnected from (authenticating|invalid) user \S+ [\da-fA-F.:]*(?:%\S+)? port \d*/ ) or ($ThisLine =~ /Disconnecting( (authenticating|invalid) user .* port \d+)?: Too many authentication failures \[preauth\]/ ) or ($ThisLine =~ /Disconnecting( (authenticating|invalid) user .* port \d+)?: Change of username or service not allowed: .* \[preauth\]/ ) or ($ThisLine =~ /Failed to release session: Interrupted system call/) or ($ThisLine =~ /Close session: user /) or #($ThisLine =~ /error: .*: banner line contains invalid characters/) or #($ThisLine =~ /past MaxStartups/) or #($ThisLine =~ /exited MaxStartups throttling/) or # user already accounted for in other statement ($ThisLine =~ /^input_userauth_request: (illegal|invalid) user (.*)(?: \[preauth\])?$/ ) or 0 # This line prevents blame shifting as lines are added above ) { # Ignore these } elsif ( ($Method,$User,$Host,$Port,$Key,$FingerP) = ($ThisLine =~ /^Accepted (\S+) for ((?:invalid user )?\S+) from ([\d\.:a-f]+)(?:%\w+)? port (\d+) ssh[12](?:: (\w+) (.+))?/) ) { if ($Debug >= 5) { print STDERR "DEBUG: Found -$User logged in from $Host using $Method ($Key)\n"; } if ($Detail >= 20) { $Users{$User}{$Host}{$Method . ($Key ? "($Key" . (($Detail >= 30) ? " " . $FingerP : "") . ")" : "")}++; } else { if ( $Host !~ /$IgnoreHost/ ) { $Users{$User}{$Host}{"(all)"}++; } } } elsif ( ($Method, undef,$User,$Host,$Port) = ($ThisLine =~ m/^Failed (\S+) for (illegal|invalid) user (.*) from ([^ ]+) port (\d+)/ ) ) { #openssh $IllegalUsers{$Host}{$User}++; } elsif ( ($User) = ( $ThisLine =~ /Disconnecting: Too many authentication failures for ([^ ]+)/)) { $TooManyFailures{$User}++; } elsif ( ($User) = ( $ThisLine =~ /error: maximum authentication attempts exceeded for ([^ ]+) from [^ ]+ port \d+ ssh2 \[preauth\]/)) { $TooManyFailures{$User}++; } elsif ( ($User,$Host) = ( $ThisLine =~ /error: maximum authentication attempts exceeded for invalid user ([^ ]+) from ([^ ]+) port \d+ ssh2 \[preauth\]/)) { $IllegalUsers{$Host}{$User}++; } elsif ( $ThisLine =~ m/^(fatal: )?Did not receive ident(ification)? string from (\S+)/ ) { # ssh/openssh my $name = LookupIP($3); $NoIdent{$name}++; } elsif ( ($Host) = ($ThisLine =~ /Could not write ident string to ([^ ]+)$/ )) { my $name = LookupIP($Host); $NoIdent{$name}++; } elsif ( ($ThisLine =~ m/^(?:error:.*|fatal:) Connection closed by remote host/ ) or ($ThisLine =~ m/^(|fatal: )Read error from remote host(| [^ ]+)(| port \d+): Connection reset by peer/ ) or ($ThisLine =~ m/^error: .*: read: Connection reset by peer/ ) or ($ThisLine =~ m/^error: .*: write: Broken pipe/ ) or ($ThisLine =~ m/^Read error from remote host [^ ]+(| port \d+): (Connection timed out|No route to host)/ ) or ($ThisLine =~ m/^fatal: Read from socket failed: No route to host/) or ($ThisLine =~ m/^fatal: Write failed: Network is unreachable/ ) or ($ThisLine =~ m/^fatal: Write failed: Broken pipe/) or ($ThisLine =~ m/^fatal: Write failed: Connection reset by peer/) or ($ThisLine =~ m/^Connection reset by/) or ($ThisLine =~ m/^channel \d+: open failed: (?:connect failed: Channel open failed\.|administratively prohibited: open failed)/) or ($ThisLine =~ m/^session_input_channel_req: no session \d+ req window-change/) or ($ThisLine =~ m/^error: chan_shutdown_read failed for .+/) ) { $NetworkErrors++; } elsif ( $ThisLine =~ m/^(log: )?Received (signal 15|SIG...); (terminating|restarting)\./) { #ssh/openssh $Kills++; if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Signal 15 Terminating- line\n"; } } elsif ( ($Port) = ($ThisLine =~ m/^(log: )?Server listening on(( [^ ]+)? port \d+)/ ) ) { #ssh/openssh $Starts++; if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Listening on ${Port}- line\n"; } } elsif ( ($Port,$Address,$Reason) = ($ThisLine =~ /^error: Bind to port ([^ ]+) on ([^ ]+) failed: (.+).$/ )) { my $Temp = "$Address port $Port ($Reason)"; # Failed to bind on 0.0.0.0 likely due to configured "ListenAddress" # on both IPv4 and IPv6 unless ($Address =~ /^0.0.0.0$/) { $BindFailed{$Temp}++; } } elsif ( $ThisLine =~ m/^(log: )?Generating .* \w+ key\./ ) { # ssh/openssh # Don't care about this... if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Generating RSA key- line\n"; } } elsif ( $ThisLine =~ m/^packet_set_maxsize: /) { if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -packet_set_maxsize- line\n"; } } elsif ( $ThisLine =~ m/^(log: )?\w+ key generation complete\./ ) { # ssh/openssh # Don't care about this... if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Keygen complete- line\n"; } } elsif ( ($Method,$User,$Host,undef) = ( $ThisLine =~ m/^Failed (\S+) for (\S+) from ([^ ]+) port (\d+)/ ) ) { #openssh # depending on log mode, openssh may not report these in connection context. if ( $Debug >= 5 ) { print STDERR "DEBUG: Found -Failed login- line\n"; } $BadLogins{$Host}{"$User/$Method"}++; } elsif ($ThisLine =~ s/^(log: )?Could not reverse map address ([^ ]*).*$/$2/) { $NoRevMap{$ThisLine}++; } elsif ( ($Address) = ($ThisLine =~ /^reverse mapping checking getaddrinfo for (\S+( \[\S+\])?) failed - POSSIBLE BREAK-IN ATTEMPT!/)) { $NoRevMap{$Address}++; } elsif ( ($IP,$Address) = ($ThisLine =~ /^Address ([^ ]*) maps to ([^ ]*), but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!/)) { $NoRevMap{"$Address($IP)"}++; } elsif ( (undef,$Address) = ($ThisLine =~ /^warning: ([^ ]*), line \d+: can't verify hostname: getaddrinfo\(([^ ]*), AF_INET\) failed$/)) { $NoRevMap{$Address}++; } elsif ( (undef,$Addresses) = ($ThisLine =~ /^warning: ([^ ]*), line \d+: host [^ ]* mismatch: (.*)$/)) { $MisMatch{$Addresses}++; } elsif ( $ThisLine =~ m/subsystem request for sftp/ ) { $sftpRequests++; } elsif ( $ThisLine =~ m/refused connect from (.*)$/ ) { $RefusedConnections{$1}++; } elsif ( ($Reason) = ($ThisLine =~ /^Authentication refused: (.*)$/ ) ) { $RefusedAuthentication{$Reason}++; } elsif ( (undef,$Host,$Port,$Reason,$Offer) = ($ThisLine =~ /^(fatal: )?Unable to negotiate with ([^ ]+)( port \d+)?: (.*)\. Their offer: (.*) \[preauth\]$/) ) { $NegotiationFailed{$Reason}{$Host}{$Offer}++; } elsif ( ($Reason,$Host,$Offer) = ($ThisLine =~ /^(Protocol major versions differ) for ([^ ]+)(?: port \d+)?: (.*)$/) ) { $NegotiationFailed{$Reason}{$Host}{$Offer}++; } elsif ( ($Reason,$Offer) = ($ThisLine =~ /^error: (Protocol major versions differ): (.*)$/) ) { $multiline++; } elsif ( ($Reason,$Offer) = ($ThisLine =~ /^error: (kex_exchange_identification): (.*)$/) ) { $multiline++; } elsif ( ($Reason,$Offer) = ($ThisLine =~ /^error: (kex protocol error|key_protocol_error): (.*)$/) ) { $multiline++; } elsif ( ($Reason,$Offer) = ($ThisLine =~ /^(userauth_pubkey): (.*)( \[preaut\])?$/) ) { $multiline++; } elsif ( ($Prio,$Host,$Port,$Code,$Reason) = ($ThisLine =~ /^(error: )?Received disconnect from ([^ ]*)( port \d+)?: ?(\d+): (.*)$/)) { # Reason 11 ({SSH,SSH2}_DISCONNECT_BY_APPLICATION) is expected, and logged at severity level INFO if (($Reason =~ /preauth/) || ($Code != 11) || ($Detail >= 30)) { $DisconnectReceived{$Reason}{$Host}++; } } elsif ( ($Host) = ($ThisLine =~ /^ROOT LOGIN REFUSED FROM ([^ ]*)$/)) { $RootLogin{$Host}++; } elsif ( ($Error) = ($ThisLine =~ /^Cannot release PAM authentication\[\d\]: (.*)$/)) { $PamReleaseFail{$Error}++; } elsif ( ($Error) = ($ThisLine =~ /^pam_systemd\(sshd:session\): Failed to release session: (.*)$/)) { $PamReleaseFail{$Error}++; } elsif ( ($Error) = ( $ThisLine =~ m/^error: PAM: (.*)$/)) { $PamError{$Error}++; } elsif ( ($Error) = ( $ThisLine =~ m/pam_systemd\(sshd:session\): (Failed to create session: .*)$/)) { $PamError{$Error}++; } elsif ( ($Reason) = ( $ThisLine =~ m/pam_chroot\(.+\):\s+([^:])/)) { $PamChroot{$Reason}++; } elsif ( ($Error) = ( $ThisLine =~ m/^error: Could not get shadow information for (.*)$/)) { $ShadowInfo{$Error}++; } elsif ( ($Reason) = ($ThisLine =~ /^Setting tty modes failed: (.*)$/)) { $TTYModesFail{$Reason}++; } elsif ( ($User,undef) = ($ThisLine =~ /^User ([^ ]*) not allowed because ([^ ]*) exists$/)) { $LoginLock{$User}++; } elsif ( ($Method,$InvaUser,$IlegUser,$EmptyUser,$User,$Host) = ($ThisLine =~ /^Postponed ([^ ]*) for ((invalid user) [^ ]*|(illegal user) [^ ]*|([^ ]*)) from ([^ ]*) port \d+ ssh/)) { $PostPonedAuth{"$User/$Method"}{$Host}++; if ($IlegUser =~ /illegal user/) {$IllegalUsers{$Host}{$User}++;} } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*) not allowed because account is locked/)) { $LockedAccount{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*) from (?:[^ ]*) not allowed because not listed in AllowUsers/)) { $AllowUsers{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*)( from [0-9.]*)? not allowed because listed in DenyUsers/)){ $DenyUsers{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*)( from [0-9.]*)? not allowed because not in any group/)) { $NoGroups{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*)( from [^ ]*)? not allowed because a group is listed in DenyGroups/)) { $DenyGroups{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*) from ([^ ]*) not allowed because none of user's groups are listed in AllowGroups/)) { $AllowGroups{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*) not allowed because shell (\S+) does not exist/)) { $NoShellUsers{$User}++; } elsif ( ($User) = ($ThisLine =~ /^User ([^ ]*) not allowed because shell (\S+) is not executable/)) { $ShellNotExecutableUsers{$User}++; } elsif ( ($User) = ($ThisLine =~ /^fatal: Access denied for user ([^ ]+) by PAM account configuration \[preauth\]/)) { $PamDeny{$User}++; } elsif ( ($IP) = ($ThisLine =~ /^scanned from ([^ ]*)/) ) { push @Scanned, $IP; } elsif ( (undef,$Line,$Option) = ($ThisLine =~ /^re(xec|process config) line (\d+): Deprecated option (.*)$/)) { $DeprecatedOption{"$Option - line $Line"}++; } elsif ( ($Pom1,$Pom2,$User) = ($ThisLine =~ /pam_krb5(\[\d*\])?: authentication fails for (`|')([^ ]*)'/)) { $KrbAutFail{$User}++; } elsif ( ($Error) = ($ThisLine =~ /pam_krb5: authenticate error: (.*)$/)) { $KrbAutErr{$Error}++; } elsif ( ($ThisLine =~ /pam_krb5: unable to determine uid\/gid for user$/)) { $KrbAutErr{"unable to determine uid/gid for user"}++; } elsif ( ($Error) = ($ThisLine =~ /pam_krb5: error removing file (.*)$/)) { $KrbErr{"error removing file " . $Error}++; } elsif ( ($Pom,$Error) = ($ThisLine =~ /pam_krb5(\[\d*\]): error resolving user name '[^ ]*' to uid\/gid pai/)) { $KrbErr{"error resolving user name '$Error' to uid\/gid pai"}++; } elsif ( (undef,$User,$Host) = ($ThisLine =~ m/^(Illegal|Invalid) user (.*) from ([^ ]+)/ )) { if ($User eq "") { $User = "{undefined}"; } $IllegalUsers{$Host}{$User}++; } elsif (($File,$Perm,$Why) = ($ThisLine =~ /error: chmod (.*) (.*) failed: (.*)/)) { $ChmodErr{"$File,$Perm,$Why"}++; } elsif (($File,$From,$To,$Why) = ($ThisLine =~ /error: chown (.*) (.*) (.*) failed: (.*)/)) { $ChownErr{"$File,$From,$To,$Why"}++; } elsif (($user,$realm) = ($ThisLine =~ /Authorized to ([^ ]+), krb5 principal \1@([^ ]+) \((?:krb5_kuserok|ssh_gssapi_krb5_cmdok)\)/)) { $Krb_realm{$realm}{$user}++; } elsif (($Action,$User) = ($ThisLine =~ /^session ((?:open|clos)ed) for local user (\S+) from /)) { $Session{"Action,$User"}++; } elsif ($ThisLine =~ /^sent status No such file$/) { $StatusNoSuchFile++; } elsif (($File,$Flags,$Mode) = ($ThisLine =~ /^open "(.*)" flags (\S+) mode (\d+)/ )) { $OpenFile{"$File:$Flags:$Mode"}++; } elsif (($File,$BytesRead,$BytesWritten) = ($ThisLine =~ /^close "(.*)" bytes read (\d+) written (\d+)/ )) { $CloseFileReadWrite{"$File,$BytesRead,$BytesWritten"}++; } elsif (($Sent,$Received) = ($ThisLine =~ /^Transferred: sent (\d+), received (\d+) bytes$/ )) { $BytesSent += $Sent; $BytesReceived += $Received; } elsif (($File,$Modtime) = ($ThisLine =~ /^set "(.*)" modtime (\d+-\d+:\d+:\d+)$/ )) { $SetModtime{"$Modtime,$File"}++; } elsif (($Dir) = ($ThisLine =~ /^opendir "(.*)"$/ )) { $OpenDir{$Dir}++; } elsif (($Dir) = ($ThisLine =~ /^closedir "(.*)"$/ )) { $CloseDir{$Dir}++; } elsif (($File) = ($ThisLine =~ /^realpath "(.*)"$/ )) { $RealPath{$File}++; } elsif (($File) = ($ThisLine =~ /^stat name "(.*)"$/ )) { $Stat{$File}++; } elsif (($Dir) = ($ThisLine =~ /^Changed root directory to "(.*)"/ )) { $Chroot{$Dir}++; } elsif (($ClientVer) = ($ThisLine =~ /^received client version (\S+)/ )) { $ClientVers{$ClientVer}++; } elsif (($Host,$Port) = ($ThisLine =~ /^error: connect_to (\S+) port (\d+): failed\.$/)) { $ConnectFailed{"$Host port $Port"}++; } elsif ($ThisLine =~ /^fatal: no matching cipher found: /) { $NoCipher++; } elsif ($ThisLine =~ /beginning MaxStartups throttling/) { $MaxStartupsThrottling++; } elsif (($IP) = ($ThisLine =~ /drop connection #\d+ from \[(\S+)\]:\d+ on \[\S+\]:\d+ past MaxStartups/)) { $MaxStartupsIPs{$IP}++; } elsif (($hr, $min, $sec, $conn) = ($ThisLine =~ /exited MaxStartups throttling after (\d\d):(\d\d):(\d\d), (\d+) connections dropped/)) { $MaxStartupsTime += (((($hr * 60) + $min) * 60) + $sec); $MaxStartupsDrops += $conn; } elsif (($IP,$Reason) = ($ThisLine =~ /drop connection #\d+ from \[(\S+)\]:\d+ on \[\S+\]:\d+ penalty: (.*)/)) { $DroppedConnections{$IP . " - " . $Reason}++; } else { # Report any unmatched entries... unless ($ThisLine =~ /fwd X11 connect/) { $OtherList{$ThisLine} += 1; } } } ########################################################### sub timesplural { my ($count) = @_; my $plural = ($count > 1) ? "s" : ""; return "$count Time$plural\n"; } if ($NetworkErrors) { print "\nNetwork Read Write Errors: " . $NetworkErrors . "\n"; } if ($Kills) { print "\nSSHD Killed: " . timesplural($Kills); } if ($Starts) { print "\nSSHD Started: " . timesplural($Starts); } if (keys %DeprecatedOption) { print "\nDeprecated options in SSH config:\n"; foreach my $Option (sort {$a cmp $b} keys %DeprecatedOption) { print " $Option\n"; } } if (keys %RootLogin) { print "\n\nWARNING!!!\n"; print "Refused ROOT login attempt from:\n"; foreach my $Host (sort {$a cmp $b} keys %RootLogin) { print " $Host : " . timesplural($RootLogin{$Host}); } } if (keys %BindFailed) { print "\nFailed to bind:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %BindFailed) { print " $ThisOne : " . timesplural($BindFailed{$ThisOne}); } } if ($Detail >= 30 && keys %ConnectFailed) { # SSH Socks Forwarding print "\nFailed to connect to:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ConnectFailed) { print " $ThisOne : " . timesplural($ConnectFailed{$ThisOne}); } } if ($Detail >= 10) { if (keys %NoRevMap) { print "\nCouldn't resolve these IPs:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %NoRevMap) { print " $ThisOne: " . timesplural($NoRevMap{$ThisOne}); } } if (keys %NoIdent) { print "\nDidn't receive an ident from these IPs:\n"; foreach my $ThisOne (sort {$a cmp $b} keys %NoIdent) { print " $ThisOne: " . timesplural($NoIdent{$ThisOne}); } } if (keys %MisMatch) { print "\nMismatched host names and/or IPs:\n"; foreach my $ThisOne (sort keys %MisMatch) { print " $ThisOne: " . timesplural($MisMatch{$ThisOne}); } } } if (keys %NegotiationFailed) { print "\nNegotiation failed:\n"; foreach my $Reason (sort {$a cmp $b} keys %NegotiationFailed) { my $Total = 0; print " $Reason"; if ( $Detail > 0 ) { print "\n"; } foreach my $Host (sort {$a cmp $b} keys %{$NegotiationFailed{$Reason}}) { my $HostTotal = 0; foreach my $Offer (sort {$a cmp $b} keys %{$NegotiationFailed{$Reason}{$Host}}) { $HostTotal += $NegotiationFailed{$Reason}{$Host}{$Offer}; } $Total += $HostTotal; if ( $Detail > 0 ) { print " $Host: " . timesplural($HostTotal); } if ( $Detail > 5 ) { foreach my $Offer (sort {$a cmp $b} keys %{$NegotiationFailed{$Reason}{$Host}}) { my $tot = $NegotiationFailed{$Reason}{$Host}{$Offer}; print " $Offer: " . timesplural($tot); } } } if ( $Detail == 0 ) { print ": " . timesplural($Total); } } } if ($NoCipher && $Detail > 0) { print "\nNo matching cipher offered: " . timesplural($NoCipher); } if (keys %TooManyFailures) { print "\nDisconnecting after too many authentication failures for user:\n"; foreach my $User (sort {$a cmp $b} keys %TooManyFailures) { print " $User : " . timesplural($TooManyFailures{$User}); } } if (keys %BadLogins) { print "\nFailed logins from:\n"; foreach my $ip (sort SortIP keys %BadLogins) { my $name = LookupIP($ip); my $totcount = 0; foreach my $user (keys %{$BadLogins{$ip}}) { $totcount += $BadLogins{$ip}{$user}; } print " $name: ". timesplural($totcount); if ($Detail >= 5) { my $sort = CountOrder(%{$BadLogins{$ip}}); foreach my $user (sort $sort keys %{$BadLogins{$ip}}) { my $val = $BadLogins{$ip}{$user}; print " $user: " . timesplural($val); } } } } if (keys %IllegalUsers) { print "\nIllegal users from"; if ($IllegalUsersThreshold) { print " (with threshold >= $IllegalUsersThreshold)"; } print ":\n"; foreach my $ip (sort SortIP keys %IllegalUsers) { my $name = LookupIP($ip); my $totcount = 0; foreach my $user (keys %{$IllegalUsers{$ip}}) { $totcount += $IllegalUsers{$ip}{$user}; } if ($IllegalUsersThreshold == 0 || $totcount >= $IllegalUsersThreshold) { print " $name: " . timesplural($totcount); if ($Detail >= 5) { my $sort = CountOrder(%{$IllegalUsers{$ip}}); foreach my $user (sort $sort keys %{$IllegalUsers{$ip}}) { my $val = $IllegalUsers{$ip}{$user}; print " $user: " . timesplural($val); } } } } } if (keys %LockedAccount) { print "\nLocked account login attempts:\n"; foreach my $User (sort {$a cmp $b} keys %LockedAccount) { print " $User : " . timesplural($LockedAccount{$User}); } } if (keys %AllowUsers) { print "\nLogin attempted when not in AllowUsers list:\n"; foreach my $User (sort {$a cmp $b} keys %AllowUsers) { print " $User : " . timesplural($AllowUsers{$User}); } } if (keys %DenyUsers) { print "\nLogin attempted when in DenyUsers list:\n"; foreach my $User (sort {$a cmp $b} keys %DenyUsers) { print " $User : . " . timesplural($DenyUsers{$User}); } } if (keys %AllowGroups) { print "\nLogin attempted when not in AllowGroups list:\n"; foreach my $User (sort {$a cmp $b} keys %AllowGroups) { print " $User : " . timesplural($AllowGroups{$User}); } } if (keys %DenyGroups) { print "\nLogin attempted when in DenyGroups list:\n"; foreach my $User (sort {$a cmp $b} keys %DenyGroups) { print " $User : " . timesplural($DenyGroups{$User}); } } if (keys %PamDeny) { print "\nLogin attempted when denied by PAM configuration:\n"; foreach my $User (sort {$a cmp $b} keys %PamDeny) { print " $User : " . timesplural($PamDeny{$User}); } } if (keys %NoGroups) { print "\nLogin attempted when user is in no group:\n"; foreach my $User (sort {$a cmp $b} keys %NoGroups) { print " $User : " . timesplural($NoGroups{$User}); } } if (keys %NoShellUsers) { print "\nLogin attempted when shell does not exist:\n"; foreach my $User (sort {$a cmp $b} keys %NoShellUsers) { print " $User : " . timesplural($NoShellUsers{$User}); } } if (keys %ShellNotExecutableUsers) { print "\nLogin attempted when shell is not executable:\n"; foreach my $User (sort {$a cmp $b} keys %ShellNotExecutableUsers) { print " $User : " . timesplural($ShellNotExecutableUsers{$User}); } } if ((keys %LoginLock) and ($Detail >= 5)) { print "\nUser login attempt when nologin was set:\n"; foreach my $User (sort {$a cmp $b} keys %LoginLock) { print " $User : " . timesplural($LoginLock{$User}); } } if (keys %PostPonedAuth) { print "\nPostponed authentication:\n"; foreach my $User (sort {$a cmp $b} keys %PostPonedAuth) { print " $User:\n"; foreach my $Host (sort {$a cmp $b} keys %{$PostPonedAuth{$User}}) { print " $Host: " . timesplural($PostPonedAuth{$User}{$Host}); } } } if (keys %Users) { print "\nUsers logging in through sshd:\n"; foreach my $user (sort {$a cmp $b} keys %Users) { print " $user:\n"; if ($Detail < 20) { my $totalSort = TotalCountOrder(%{$Users{$user}}, \&SortIP); foreach my $ip (sort $totalSort keys %{$Users{$user}}) { my $name = LookupIP($ip); my $val = (values %{$Users{$user}{$ip}})[0]; print " $name: " . timesplural($val); } } else { my %Methods = (); foreach my $ip (keys %{$Users{$user}}) { foreach my $method (keys %{$Users{$user}{$ip}}) { $Methods{$method}{$ip} = $Users{$user}{$ip}{$method}; } } if (scalar keys %{$Users{$user}} < scalar %Methods) { my $totalSort = TotalCountOrder(%{$Users{$user}}, \&SortIP); foreach my $ip (sort $totalSort keys %{$Users{$user}}) { my $name = LookupIP($ip); print " $name:\n"; my $sort = CountOrder(%{$Users{$user}{$ip}}); foreach my $method (sort $sort keys %{$Users{$user}{$ip}}) { my $val = $Users{$user}{$ip}{$method}; print " $method: " . timesplural($val); } } } else { my $totalSort = TotalCountOrder(%Methods); foreach my $method (sort $totalSort keys %Methods) { print " $method:\n"; my $sort = CountOrder(%{$Methods{$method}}); foreach my $ip (sort $sort keys %{$Methods{$method}}) { my $name = LookupIP($ip); my $val = (values %{$Users{$user}{$ip}})[0]; print " $name: " . timesplural($val); } } } } } } if (keys %RefusedAuthentication) { print "\nAuthentication refused:\n"; foreach my $Reason (sort {$a cmp $b} keys %RefusedAuthentication) { print " $Reason : " . timesplural($RefusedAuthentication{$Reason}); } } if (keys %KrbAutFail) { print "\n Failed pam_krb5 authentication:\n"; foreach my $User (sort keys %KrbAutFail) { print " $User: " . timesplural($KrbAutFail{$User}); } } if (keys %KrbAutErr) { print "\n pam_krb5 authentication errors:\n"; foreach my $Error (sort keys %KrbAutErr) { print " $Error: " . timesplural($KrbAutErr{$Error}); } } if (keys %KrbErr) { print "\n pam_krb5 errors:\n"; foreach my $Error (sort keys %KrbErr) { print " $Error: " . timesplural($KrbErr{$Error}); } } if (keys %DisconnectReceived) { print "\nReceived disconnect:\n"; foreach my $Reason (sort {$a cmp $b} keys %DisconnectReceived) { my $Total = 0; print " $Reason"; foreach my $Host (sort {$a cmp $b} keys %{$DisconnectReceived{$Reason}}) { $Total += $DisconnectReceived{$Reason}{$Host}; if( $Detail > 0 ) { print "\n $Host : $DisconnectReceived{$Reason}{$Host} Time(s)"; } } if( $Detail > 0 ) { print "\n"; } else { print " : " . timesplural($Total); } } } if ($#Scanned >= 0) { print "\nScanned from:\n"; foreach my $ThisOne (sort SortIP @Scanned) { print " " . LookupIP($ThisOne) . "\n"; } } if (keys %RefusedConnections) { my $output; foreach my $badguy (sort {$a cmp $b} keys %RefusedConnections ) { if ($RefusedConnectionsThreshold == 0 || $Detail > 5 || $RefusedConnections{$badguy} >= $RefusedConnectionsThreshold) { $output .= " $badguy: " . timesplural($RefusedConnections{$badguy}); } } if ($output ne '') { print "\nRefused incoming connections:\n"; print $output; } } if ($MaxStartupsThrottling) { print "\nMaxStartups throttling begins: " . timesplural($MaxStartupsThrottling); if ($Detail >= 5) { print " Dropped connections total: " . timesplural($MaxStartupsDrops); my $hr = $MaxStartupsTime / 3600; my $min = ($MaxStartupsTime / 60) % 60; my $sec = $MaxStartupsTime % 60; printf " Throttling time total: %02d:%02d:%02d\n", $hr, $min, $sec; } if ($Detail >= 10) { print " Initial dropped IP:\n"; foreach my $ThisOne (sort SortIP keys %MaxStartupsIPs) { print " " . LookupIP($ThisOne) . " : " . timesplural($MaxStartupsIPs{$ThisOne}); } } } if (keys %DroppedConnections) { foreach my $Dropped (sort keys %DroppedConnections) { print " $Dropped: " . timesplural($DroppedConnections{$Dropped}); } } if (keys %PamReleaseFail) { print "\nCannot release PAM authentication:\n"; foreach my $Error (sort {$a cmp $b} keys %PamReleaseFail) { print " $Error : " . timesplural($PamReleaseFail{$Error}); } } if (keys %ShadowInfo) { print "\nCould not get shadow information for:\n"; foreach my $Error (sort {$a cmp $b} keys %ShadowInfo) { print " $Error : " . timesplural($ShadowInfo{$Error}); } } if (keys %PamError) { print "\nError in PAM authentication:\n"; foreach my $Error (sort {$a cmp $b} keys %PamError) { print " $Error : " . timesplural($PamError{$Error}); } } if (keys %PamChroot) { print "\nPAM chroot:\n"; foreach my $Reason (sort {$a cmp $b} keys %PamChroot) { print " $Reason : " . timesplural($PamChroot{$Reason}); } } if (keys %TTYModesFail) { print "\nSetting tty modes failed:\n"; foreach my $Reason (sort {$a cmp $b} keys %TTYModesFail) { print " $Reason : " . timesplural($TTYModesFail{$Reason}); } } if ($sftpRequests > 0) { print "\nSFTP subsystem requests: $sftpRequests Time(s)\n"; if ($Detail >= 50) { foreach my $root (sort {$a cmp $b} keys %Chroot) { print " Chroot: $root : " . timesplural($Chroot{$root}); } } if ($Detail >= 0) { foreach my $dir (sort {$a cmp $b} keys %CloseDir) { if ($CloseDir{$dir} != $OpenDir{$dir} || $Detail >= 40) { print " Directory $dir: opened/closed $OpenDir{$dir}/$CloseDir{$dir} Time(s)\n"; # Note: this number might not match if the open/closes span log windows..." } } } if ($Detail >= 30) { foreach my $details (sort {$a cmp $b} keys %CloseFileReadWrite) { if (my ($file,$read,$written) = ($details =~ /^(.*),(\d+),(\d+)$/)) { if ($read || $written || $Detail >= 40) { print " Read/Wrote $read/$written byte(s) from file $file\n"; } } } } if ($Detail >= 60) { foreach my $details (sort {$a cmp $b} keys %OpenFile) { if (my ($file,$flags,$mode) = ($details =~ /^(.*):([^:]*):(\d+)$/)) { print " Opened (mode: $mode, flags: $flags) file $file\n"; } } } if ($Detail >= 70) { foreach my $path (sort {$a cmp $b} keys %RealPath) { print " Realpath $path\n"; } } if ($Detail >= 50) { foreach my $details (sort {$a cmp $b} keys %SetModtime) { if (my ($modtime,$file) = ($details =~ /^([^,]*),(.*)$/)) { print " Set modtime $modtime for file $file\n"; } } } if ($Detail >= 70) { foreach my $path (sort {$a cmp $b} keys %Stat) { print " Stat: $path : " . timesplural($Stat{$path}); } } if ($Detail >= 40) { if ($StatusNoSuchFile) { print " Sent status No such file: " . timesplural($StatusNoSuchFile); } } if ($Detail >= 0) { if ($BytesSent || $BytesReceived) { print " Transferred: sent/received $BytesSent/$BytesReceived byte(s)\n"; } } if ($Detail >= 50) { foreach my $client (sort {$a cmp $b} keys %ClientVers) { print " Client version $client connected " . timesplural($ClientVers{$client}); } } } if (keys %ChmodErr) { print "\nChmod errors:\n"; foreach (sort keys %ChmodErr) { my ($File,$Perm,$Why)= split ","; print " " . $File . " " . $Perm . " failed(" . $Why . "): " . timesplural($ChmodErr{"$File,$Perm,$Why"}); } } if (keys %ChownErr) { print "\nChown errors:\n"; foreach (keys %ChownErr) { my ($File,$From,$To,$Why)= split ","; print " " . $File . " " . $From . " " .$To . " failed(" . $Why . "): " . timesplural($ChmodErr{"$File,$From,$To,$Why"}); } } if ( ($Detail == 7 && keys %Krb_realm > 1) || ($Detail > 7 && keys %Krb_realm) ){ print "\nSuccessful Kerberos Authentication from ",(scalar keys %Krb_realm)," realm:\n"; foreach my $realm (sort keys %Krb_realm) { if($Detail > 9){ print " ",$realm,":\n"; foreach my $user(sort keys %{$Krb_realm{$realm}}) { print " ",$user,": " . timesplural($Krb_realm{$realm}{$user}); } } else { print " ",$realm,": ". (scalar keys %{$Krb_realm{$realm}}) . " User(s)\n"; } } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; print "$_ : " . timesplural($OtherList{$_}) foreach sort keys %OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/arpwatch0000644000175000017500000000321615075123620020530 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; #Init Hash my %ARPWatch; while (defined(my $ThisLine = )) { chomp($ThisLine); next if ($ThisLine eq ""); $ARPWatch{$ThisLine}++; } if ( ($Detail >= 10) and (keys %ARPWatch) ) { print "\n"; foreach my $ThisOne (sort {$a cmp $b} keys %ARPWatch) { print $ThisOne . "\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/tac_acc0000644000175000017500000001006413420545445020300 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # frank@fam-breedijk.com. # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ######################################################## use strict; my ($ThisLine, @fields, %activity, %isdn, @OtherList); my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; while (defined($ThisLine = )) { @fields = split /\t/, $ThisLine; if ( @fields == 11 && $fields[10] =~ /^cmd=/ ) { unless ( exists $activity{$fields[1]} ) { $activity{$fields[1]} = {}; } unless ( exists $activity{$fields[1]}->{$fields[2]} ) { $activity{$fields[1]}->{$fields[2]} = {}; } chomp $fields[10]; $fields[10] =~ s/^cmd=//; $activity{$fields[1]}->{$fields[2]}->{$fields[10]}++; } elsif ( @fields == 12 && $fields[11] =~ /^cmd=/ ) { unless ( exists $activity{$fields[1]} ) { $activity{$fields[1]} = {}; } unless ( exists $activity{$fields[1]}->{$fields[2]} ) { $activity{$fields[1]}->{$fields[2]} = {}; } chomp $fields[11]; $fields[11] =~ s/^cmd=//; $activity{$fields[1]}->{$fields[2]}->{$fields[11]}++; } elsif ( @fields == 27 && $fields[24] =~ /^elapsed_time=/ ) { unless ( exists $isdn{$fields[2]} ) { $isdn{$fields[2]} = {}; } unless ( exists $isdn{$fields[2]}->{$fields[4]} ) { $isdn{$fields[2]}->{$fields[4]} = {}; $isdn{$fields[2]}->{$fields[4]}->{'seconds'} = 0; } chomp $fields[24]; $fields[24] =~ s/^elapsed_time=//; $isdn{$fields[2]}->{$fields[4]}->{'seconds'} += $fields[24]; } elsif ( @fields == 10 && $fields[5] =~ /^start/ ) { unless ( exists $isdn{$fields[2]} ) { $isdn{$fields[2]} = {}; } unless ( exists $isdn{$fields[2]}->{$fields[4]} ) { $isdn{$fields[2]}->{$fields[4]}->{'start'} = 0; } $isdn{$fields[2]}->{$fields[4]}->{'start'}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } if ((keys %activity) and ($Detail >= 5)) { foreach my $host ( sort keys %activity ) { print "\nActivity on $host:\n"; foreach my $user ( sort keys %{$activity{$host}} ) { print "User: $user\n"; foreach my $command ( sort keys %{$activity{$host}->{$user}} ) { print "$command\t$activity{$host}->{$user}->{$command} time(s)\n"; } } } } if ((keys %isdn) and ($Detail >= 5)) { foreach my $host ( sort keys %isdn ) { print "\nISDN on $host:\n"; foreach my $number ( sort keys %{$isdn{$host}} ) { print "Number: $number dialed total $isdn{$host}->{$number}->{'seconds'} seconds"; if ($isdn{$host}->{$number}->{'start'}) { print " started $isdn{$host}->{$number}->{'start'} times\n"; } else { print "\n"; } } } } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/slon0000644000175000017500000001065115075123620017673 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Jeff Frost # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # This was written and is maintained by: # Jeff Frost # # Heavily based on xntpd script # # This script and config file assumes that slon is # logging to /var/log/messages, if not, you need to # update the /usr/share/logwatch/default.conf/services/slon.conf and # possibly add a new entry in /usr/share/logwatch/default.conf/logfiles ######################################################## ####################################################### ## Copyright (c) 2008 Jeff Frost ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':all'; use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $DebugCounter = 0; #Init Counters my $Errors= my $Starts= my $Kills= my $ReceivedEvent= my $ForwardConfirm= my $QueueEvent= my $SyncsProcessed= my $CleanupThread= 0; #Init Arrays my @ErrorList = (); my @OtherList = (); if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside SLON Filter \n\n"; $DebugCounter = 1; } while (defined(my $ThisLine = )) { if ( $Debug >= 5 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } chomp($ThisLine); if ( ($ThisLine =~ m/new sl_action_seq/) or ($ThisLine =~ m/new sl_rowid_seq value/) or ($ThisLine =~ m/seconds delay for first row/) or ($ThisLine =~ m/seconds until close cursor/) or ($ThisLine =~ m/syncing set \d+ with .* from provider/) or ($ThisLine =~ m/no sets need syncing/) or ($ThisLine =~ m/SYNC \d+ processing/) ) { # Ignore these } elsif ($ThisLine =~ m/main process started/) { $Starts++; } elsif ($ThisLine =~ m/exit/) { $Kills++; } elsif ($ThisLine =~ m/Received event/) { $ReceivedEvent++; } elsif ($ThisLine =~ m/forward confirm/) { $ForwardConfirm++; } elsif ($ThisLine =~ m/queue event/) { $QueueEvent++; } elsif ($ThisLine =~ m/SYNC \d+ done/) { $SyncsProcessed++; } elsif ($ThisLine =~ m/cleanupThread:/) { $CleanupThread++; } elsif ($ThisLine =~ m/ERROR/) { $Errors++; push @ErrorList, "$ThisLine\n"; } else { # Report any unmatched entries... push @OtherList, "$ThisLine\n"; } } ########################################################### if ($Detail >= 5) { if ($Kills) { print "\nSLON Killed: " . $Kills . " Time(s)\n"; } if ($Starts) { print "\nSLON Started: " . $Starts . " Time(s)\n"; } if ($CleanupThread) { print "\nSLON CleanupThread Event: " . $CleanupThread . " Time(s)\n"; } if ($ForwardConfirm) { print "\nSLON ForwardConfirm Event: " . $ForwardConfirm . " Time(s)\n"; } if ($ReceivedEvent) { print "\nSLON Received Event: " . $ReceivedEvent . " Time(s)\n"; } if ($QueueEvent) { print "\nSLON Queue Event: " . $QueueEvent . " Time(s)\n"; } if ($SyncsProcessed) { print "\nSLON Syncs Processing: " . $SyncsProcessed . " Time(s)\n"; } } if ($Errors) { print "\nSLON Errors: " . $Errors . "\n"; print @ErrorList; } if ($#OtherList >= 0) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/mysql0000644000175000017500000001162413420545426020072 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # Processes all messages and summarizes them # Each message is given with a timestamp and RMS ######################################################## ## Copyright (c) 2006 by Jeremias Reith ## Modified 2009 by Michael Baierl ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use Logwatch ':dates'; use Time::Local; use POSIX qw(strftime); my $date_format = '%y%m%d %H:%M:%S'; my $filter = TimeFilter($date_format); my $detail = exists $ENV{'LOGWATCH_DETAIL_LEVEL'} ? $ENV{'LOGWATCH_DETAIL_LEVEL'} : 0; # we do not use any Date:: package (or strptime) as they are probably not available my %month2num = ( Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5, Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11 ); # array of message categories (we do not use a hash to keep the order) # first element: category name # second element: matching regexp ($1 should contain the message) # third element: anonymous hash ref (stores message counts) my @message_categories = (['Errors', qr/\[ERROR\] (.*)$/o, {}], ['Note', qr/\[Note\] (.*)$/o, {}], ['Warnings', qr/\[Warning] (.*)$/o, {}], ['Other', qr/(.*)$/o, {}]); # counting messages while(<>) { my $line = $_; # skipping messages that are not within the requested range next unless $line =~ /^($filter)/o; $1 =~ /(\d\d)(\d\d)(\d\d)\s+(\d+):(\d+):(\d+)/; my $time; { # timelocal is quite chatty local $SIG{'__WARN__'} = sub {}; $time = timelocal($6, $5, $4, $3, $2-1, $1); } # Count lines with increasing number as one: # [Warning] Aborted connection 107194 to db: ... $line =~ s/(Aborted connection) \d+ (to db)/$1 $2/; foreach my $cur_cat (@message_categories) { if($line =~ /$cur_cat->[1]/) { my $msgs = $cur_cat->[2]; $msgs->{$1} = {count => '0', first_occurrence => $time, sum => 0, sqrsum => 0} unless exists $msgs->{$1}; $msgs->{$1}->{'count'}++; # summing up timestamps and squares of timestamps # in order to calculate the rms # using first occurrence of message as offset in calculation to # prevent an integer overflow $msgs->{$1}->{'sum'} += $time - $msgs->{$1}->{'first_occurrence'}; $msgs->{$1}->{'sqrsum'} += ($time - $msgs->{$1}->{'first_occurrence'}) ** 2; last; } } } # generating summary foreach my $cur_cat (@message_categories) { # skipping non-requested message types next unless keys %{$cur_cat->[2]}; my ($name, undef, $msgs) = @{$cur_cat}; print $name, ":\n"; my $last_count = 0; # sorting messages by count my @sorted_msgs = sort { $msgs->{$b}->{'count'} <=> $msgs->{$a}->{'count'} } keys %{$msgs}; foreach my $msg (@sorted_msgs) { # grouping messages by number of occurrence print "\n", $msgs->{$msg}->{'count'}, " times:\n" unless $last_count == $msgs->{$msg}->{'count'}; my $rms = 0; # printing timestamp print '['; if($msgs->{$msg}->{'count'} > 1) { # calculating rms $rms = int(sqrt( ($msgs->{$msg}->{'count'} * $msgs->{$msg}->{'sqrsum'} - $msgs->{$msg}->{'sum'}) / ($msgs->{$msg}->{'count'} * ($msgs->{$msg}->{'count'} - 1)))); print strftime($date_format, localtime($msgs->{$msg}->{'first_occurrence'}+int($rms/2))); print ' +/-'; # printing rms if($rms > 86400) { print int($rms/86400) , ' day(s)'; } elsif($rms > 3600) { print int($rms/3600) , ' hour(s)'; } elsif($rms > 60) { print int($rms/60) , ' minute(s)'; } else { print $rms, ' seconds'; } } else { # we have got this message a single time print strftime($date_format, localtime($msgs->{$msg}->{'first_occurrence'})); } print '] ', $msg, "\n"; $last_count = $msgs->{$msg}->{'count'}; } print "\n"; } # vi: shiftwidth=3 tabstop=3 et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/pop30000644000175000017500000004015515075123620017603 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # The pop-3 script was written by: # Paweł Gołaszewski ######################################################## ####################################################### ## Copyright (c) 2008 Paweł Gołaszewski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Debug = $ENV{'LOGWATCH_DEBUG'}; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'}; #Init Counters my $ReadSocketError = 0; my $WriteSocketError = 0; my $Startups = 0; my $Shutdowns = 0; my $AuthDrivers = 0; my $sslTempkey = 0; my $OutOfMemory = 0; #Init String Containers my ( $Box, $DownloadSize, $DownloadSize1, $DownloadSize2, $Downloaded, $Drivers, $File, $Host, $Iface, $Left, $LeftSize, $Listen, $Mbox, $Mechanism, $Size, $User, $dummy ); #Init Hashes my ( %AutoLogout, %Connect, %Connection, %DamagedMbox, %DownloadedMessages, %DownloadedMessagesSize, %ListenOn, %Login, %LoginFailed, %Logout, %Logout2, %MboxSize, %MessagesLeft, %NoApopSecret, %OtherList, %PermissionDenied, %sslMechanism ); #Make pseudo IPv6 to IPv4 sub LookupIPv46 { my $IPv4Addr; my $Addr = $_[0]; if ( ($IPv4Addr) = ($Addr =~ /::ffff:([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/ ) ) { return $IPv4Addr; } else { return $Addr; } } if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG \n\n"; } while (defined(my $ThisLine = )) { if ( ($ThisLine =~ /^auth: PAM error: Authentication failure$/) or ($ThisLine =~ /^authcontext_new_user_pass: began session for/) or ($ThisLine =~ /^cclient_steal_lock: attempting to grab c-client lock from PID \d+$/) or ($ThisLine =~ /^getpeername: Socket operation on non-socket$/) or ($ThisLine =~ /^Initializing */) or ($ThisLine =~ /^Installing */) or ($ThisLine =~ /^ioabs_tcp_post_select: client .*: connection closed by peer$/) or ($ThisLine =~ /^listener_new: (.*:\d+): cannot obtain domain suffix for this address$/) or ($ThisLine =~ /^listener_new: (.*:\d+): using fallback domain suffix `(.*)'$/) or ($ThisLine =~ /^listener_new: gethostbyaddr\((.*)\): cannot resolve name$/) or ($ThisLine =~ /^(mailbox: )?open: No such file or directory$/) or ($ThisLine =~ /^(sktbuff|maildrop): write: Broken pipe$/) or ($ThisLine =~ /^maildrop: can't read message$/) or ($ThisLine =~ /^mailbox: mailbox content has been changed$/) or ($ThisLine =~ /^(sktbuff|maildrop): write: Connection reset by peer$/) or ($ThisLine =~ /^(sktbuff|maildrop): write: Connection timed out$/) or ($ThisLine =~ /^mailspool_build_index.*: skipping c-client metadata$/) or ($ThisLine =~ /^open: Permission denied$/) or ($ThisLine =~ /^read: Broken pipe$/) or ($ThisLine =~ /^read: Connection reset by peer$/) or ($ThisLine =~ /^spgetpwnam: can't find user: */) or ($ThisLine =~ /^sptls: SSL_accept error: (-|)\d+$/) or ($ThisLine =~ /^sptls: do need at least RSA or DSA cert\/key data$/) or ($ThisLine =~ /^tpop3d shutdown succeeded$/) or ($ThisLine =~ /tpop3d startup succeeded$/) or ($ThisLine =~ /^disconnected, user=/i) or ($ThisLine =~ /^timeout, user=/i) or ($ThisLine =~ /^(connection|disconnected), ip=/io) ) { # Don't care about these... } elsif ( (($User, $Host) = ( $ThisLine =~ /^user (.*?) authenticated - (.*)$/ )) or (($User, $Host) = ( $ThisLine =~ /^fork_child: \[\d\].*\((.*)\): began session for `(.*)' with .*; child PID is \d+$/ )) or (($User, $Host) = ( $ThisLine =~ /^LOGIN, user=([^ ,]+), ip=\[([^ ,]+)\](?:, port=\[\d+\](?:, stls=\d+)?)?$/ )) ) { $Login{$User}{$Host}++; } elsif ( ($User,$Downloaded,$DownloadSize,$Left,$LeftSize) = ( $ThisLine =~ /^Stats: (.*?) (.*?) (.*?) (.*?) (.*?)$/) ) { $DownloadedMessages{$User} += $Downloaded; $DownloadedMessagesSize{$User} += $DownloadSize; $MessagesLeft{$User} = $Left; $MboxSize{$User} = $LeftSize; } elsif ( ($User, $Host) = ( $ThisLine =~ /^session ended for user (.*?) - (.*)/) ) { $Logout{$User}{$Host}++; $Logout2{$User}++; $Connection{$Host}++; } elsif ( ($Host) = ( $ThisLine =~ /^session ended - (.*)$/) or ($Host) = ( $ThisLine =~ /^LOGOUT, ip=\[(.*)\]$/) ) { $Logout{"UNKNOWN"}{$Host}++; $Connection{$Host}++; } elsif ( (($User,$Host,$DownloadSize,undef) = ( $ThisLine =~ /^connections_post_select: client \[\d+\](.*)\((.*)\): disconnected; (.*?)\/(.*?) bytes read\/written$/) ) or (($Host,$User) = ( $ThisLine =~ /^connections_post_select: client \[\d\].*\((.*)\): finished session for `(.*)' with pam/) ) ) { $DownloadedMessagesSize{$User} += $DownloadSize; $Logout{$User}{$Host}++; $Logout2{$User}++; $Connection{$Host}++; } elsif (($dummy, $User, $Host, $DownloadSize1, $DownloadSize2) = ( $ThisLine =~ /^(LOGOUT|TIMEOUT|DISCONNECTED), user=(.*?), ip=\[([^ ,]+)\](?:, port=\[\d+\])?, top=(\d+), retr=(\d+)/o)) { $DownloadedMessagesSize{$User} += $DownloadSize1 + $DownloadSize2; $Logout{$User}{$Host}++; $Logout2{$User}++; $Connection{$Host}++; if ( $ThisLine =~ /^(TIMEOUT|DISCONNECTED),/o ) { $AutoLogout{$Host}++; } } elsif (($Host) = ( $ThisLine =~ /^connections_post_select: client \[\d\](.*)\/.*: disconnected; \d+\/\d+ bytes read\/written$/) ) { $Connection{$Host}++; } elsif ( (($User,$Host) = ( $ThisLine =~ /^authentication failed for user (.*?) - (.*)/ )) or (($Host,$User) = ( $ThisLine =~ /^connection_do: client `\[\d+\](.*)\/.*': username `(.*)': \d authentication failures/ )) ) { $LoginFailed{"$Host ($User)"}++; } elsif (($User,$Host) = ( $ThisLine =~ /^LOGIN FAILED, user=([^,]*), ip=\[([0-9.:a-f]*)\]/ )) { $Host = LookupIPv46($Host); $LoginFailed{"$Host ($User)"}++; } elsif ( ($User,$Host) = ( $ThisLine =~ /^authentication failed: no such user: (.*?) - (.*)/ ) ) { $LoginFailed{"$Host (UNKNOWN: $User)"}++; } elsif ( ($User) = ( $ThisLine =~ /^auth_pam_new_user_pass: pam_authenticate\((.*)\): Authentication failure/) ){ $LoginFailed{$User}++; } elsif ( ($User) = ( $ThisLine =~ /^authcontext_new_user_pass: rejecting login attempt by `(.*)' with empty password$/) ) { $LoginFailed{"EMPTY PASSWORD: $User"}++; } elsif ( ($Mechanism) = ( $ThisLine =~ /^sptls: TLS connection established: (.*)$/ ) ) { $sslMechanism{$Mechanism}++; } elsif ($ThisLine =~ /^sptls: created \d+bit temporary [^ ].* key$/ ) { $sslTempkey++; } elsif ( ($User,$Host) = ( $ThisLine =~ /^ioabs_tls_shutdown: client \[\d\](.*)\((.*)\): underlying connection closed by peer during shutdown$/) ) { # FIXME: # What to with that?? } elsif ( (($Host) = ( $ThisLine =~ /^autologout time elapsed - (.*)$/ )) or (($Host) = ( $ThisLine =~ /^net_loop: timed out client \[\d\](.*)\// )) or (($Host) = ( $ThisLine =~ /^net_loop: timed out client \[\d\].*\((.*)\)$/ )) ) { $AutoLogout{$Host}++; } elsif ( (($File) = ( $ThisLine =~ /^can't open or create file: (.*)$/ )) or (($File) = ( $ThisLine =~ /^mailbox: can't open mailbox file: (.*)$/ )) ) { $PermissionDenied{$File}++; } elsif ( ($User, $Host) = ( $ThisLine =~ /^can't find APOP secret for user (.*?) - (.*)$/ ) ) { $NoApopSecret{$User}++; $Logout{$User}{$Host}++; $Connection{$Host}++; $Logout2{$User}++; } elsif ($ThisLine =~ /^mailbox: no memory available$/ ) { $OutOfMemory++; } elsif ( ($Mbox) = ( $ThisLine =~ /^mailbox: mailbox (.*) is damaged$/ ) or ($Mbox) = ( $ThisLine =~ /^mailbox: mailbox is damaged: (.*)$/ ) ) { $DamagedMbox{$Mbox}++; } elsif ( ($ThisLine =~ /^(sktbuff|maildrop): can't read from socket$/) or ($ThisLine =~ /^ioabs_tls_read: client .*: connection closed by peer$/) or ($ThisLine =~ /^(ioabs_tls_read|ioabs_tls_post_select): client .*: connection unexpectedly closed by peer$/) ) { $ReadSocketError++; } elsif ($ThisLine =~ /^(sktbuff|maildrop): can't write to socket$/ ) { $WriteSocketError++; } elsif ( ($Box,$Size) = ( $ThisLine =~ /^mailspool_new_from_file: indexed mailspool (.*) \((\d+) bytes\) in/ ) ) { # What to do with that? #$MboxSize{$User} = $LeftSize; } elsif ( ($Host,$Iface) = ( $ThisLine =~ /^listeners_post_select: client \[\d\](.*)\/.*: connected to local address (.*:\d+)$/ ) ) { $Connection{$Host}++; $Connect{$Iface}{$Host}++; } elsif ( ($Listen) = ( $ThisLine =~ /^parse_listeners: listening on address (.*)$/ ) ) { $ListenOn{$Listen}++; } elsif ($ThisLine =~ /^net_loop: tpop3d version \d+\.\d+\.\d+ successfully started$/ ) { $Startups++; } elsif ($ThisLine =~ /^net_loop: terminating on signal \d+$/ ) { $Shutdowns++; } elsif ( ($Drivers) = ($ThisLine =~ /^(\d+) authentication drivers successfully loaded$/) ) { $AuthDrivers = $Drivers; } else { # Report any unmatched entries... # remove PID from named messages $ThisLine =~ s/^(client [.0-9]+)\S+/$1/; chomp($ThisLine); $OtherList{$ThisLine}++; } #$LastLine = $ThisLine; } ################################################ if ($Startups > 0) { print "Startups: $Startups"; if ($AuthDrivers > 0) { print " with $AuthDrivers authentication drivers\n"; } else { print "\n"; } if (keys %ListenOn) { print "Listening on:\n"; foreach my $Listen (sort {$a cmp $b} keys %ListenOn) { print " $Listen\n"; } } } if ($Shutdowns > 0) { print "\nShutdowns: $Shutdowns\n"; } if ( ( $Detail >= 0 ) and (keys %PermissionDenied)) { print "WARNING:\n"; print "Can't open or create files:\n"; foreach my $File (sort {$a cmp $b} keys %PermissionDenied) { print " $File: $PermissionDenied{$File} Time(s)\n"; } } if ( ( $Detail >= 0 ) and (keys %DamagedMbox)) { print "WARNING:\n"; print "Damaged mailbox in your system:\n"; foreach my $Mbox (sort {$a cmp $b} keys %DamagedMbox) { print " $Mbox: $DamagedMbox{$Mbox} Time(s)\n"; } } if ( ( $Detail >= 0 ) and (keys %DamagedMbox)) { print "WARNING:\n"; print "Damaged mailbox in your system:\n"; foreach my $Mbox (sort {$a cmp $b} keys %DamagedMbox) { print " $Mbox: $DamagedMbox{$Mbox} Time(s)\n"; } } if ( ( $Detail >= 0 ) and ($OutOfMemory > 0) ) { print "\nPOP3 processes were running out of memory $OutOfMemory Time(s)\n"; } if ( ( $Detail >= 0 ) and (keys %LoginFailed)) { print "\n\n[POP3] Login failures:". "\n=========================". "\n Host (user) | # ". "\n------------------------------------------------------------- | -----------"; my $ConnCount = 0; foreach my $Host (sort keys %LoginFailed) { my $Conns = $LoginFailed{$Host}; printf "\n%61s | %11.0f", $Host, $Conns; $ConnCount += $Conns; } print "\n" . "-" x 75; printf "\n%75s\n\n\n", $ConnCount; } if ( ( $Detail >= 5 ) and (keys %Connection)) { print "\n[POP3] Connections:". "\n=========================". "\n Host | Connections". "\n------------------------------------------------------------- | -----------"; my $ConnCount = 0; foreach $Host (sort keys %Connection) { my $Conns = $Connection{$Host}; printf "\n%61s | %11.0f", $Host, $Conns; $ConnCount += $Conns; } print "\n" . "-" x 75; printf "\n%75s\n\n\n", $ConnCount; } if (keys %Logout2) { print "\n[POP3] Logout stats (in MB):". "\n============================". "\n User | Logouts | Downloaded | Mbox Size". "\n--------------------------------------- | ------- | ---------- | ----------"; my $ConnCount = 0; my $SizeAll = 0; my $DownAll = 0; foreach my $User (sort keys %Logout2) { my $Conns = $Logout2{$User}; my $Down = $DownloadedMessagesSize{$User}/(1024*1024); my $Size = $MboxSize{$User}/(1024*1024); printf "\n%39s | %7d | ", $User, $Conns; if ($Down > 0) { printf "%10.2f | ",$Down; } else { printf "%10.0f | ",$Down; } if ($Size > 0) { printf "%10.2f",$Size; } else { printf "%10.0f",$Size; } $ConnCount += $Conns; $SizeAll += $Size; $DownAll += $Down; } print "\n" . "-" x 75; printf "\n%49d | %10.2f | %10.2f\n\n\n",$ConnCount,$DownAll,$SizeAll; } if ( ( $Detail >= 10 ) and (keys %Login)) { print "\n[POP3] Successful Logins:\n"; my $LoginCount = 0; foreach my $User (keys %Login) { print " User $User: \n"; my $UserCount = 0; foreach $Host (keys %{$Login{$User}}) { my $HostCount = $Login{$User}{$Host}; print " From $Host: $HostCount Time(s)\n"; $UserCount += $HostCount; } $LoginCount += $UserCount; print " Total $UserCount Time(s)\n"; print "\n"; } print "Total $LoginCount successful logins\n\n\n"; } if ($sslTempkey > 0) { print "\nTemporary SSL key created and used $sslTempkey Time(s)\n"; } if ( ( $Detail >= 5 ) and (keys %sslMechanism)) { print "\nTLS Connection types:\n"; my $TotalConnections = 0; foreach my $Mechanism (keys %sslMechanism) { print " $Mechanism $sslMechanism{$Mechanism} Time(s)\n"; $TotalConnections += $sslMechanism{$Mechanism}; } print "Total TLS connections: $TotalConnections Time(s)\n"; } if ( ( $Detail >= 5 ) and (keys %AutoLogout)) { print "\nAutologout:\n"; foreach my $Host (sort {$a cmp $b} keys %AutoLogout) { print " $Host: $AutoLogout{$Host} Time(s)\n"; } } if ( ( $Detail >= 5 ) and (keys %NoApopSecret)) { print "\nCan't find APOP secret:\n"; my $TotalAPOP = 0; foreach my $User (keys %NoApopSecret) { print " $User: $NoApopSecret{$User} Time(s)\n"; $TotalAPOP += $NoApopSecret{$User}; } print "Total APOP errors: $TotalAPOP Time(s)\n"; } if ( ( $Detail >= 5 ) and ( $ReadSocketError > 0 ) ) { print "Socket Read Error $ReadSocketError Time(s)\n"; } if ( ( $Detail >= 5 ) and ( $WriteSocketError > 0 ) ) { print "Socket Write Error $WriteSocketError Time(s)\n"; } if ( ( $Detail >= 5 ) and ( $ReadSocketError > 0 ) ) { print "Socket Read Error $ReadSocketError Time(s)\n"; } if ( ( $Detail >= 5 ) and ( $WriteSocketError > 0 ) ) { print "Socket Write Error $WriteSocketError Time(s)\n"; } if (keys %Connect) { print "\nConnection to interface:\n"; foreach my $Iface (sort {$a cmp $b} keys %Connect) { print " $Iface:\n"; foreach my $Host (sort {$a cmp $b} keys %{$Connect{$Iface}}) { print " $Host: $Connect{$Iface}{$Host} Time(s)\n"; } } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/fetchmail0000644000175000017500000000743515075123620020662 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Oron Peled # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Oron Peled # ######################################################## ######################################################## ## Copyright (c) 2010 Oron Peled ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my %no_mail; my %messages_for; my %auth_fail; my %conn_fail; my %OtherList; #Inits while (defined(my $ThisLine = )) { chomp($ThisLine); $ThisLine =~ s/^[a-zA-Z0-9]+: //; if($ThisLine =~ s/^No mail for (\S+) at (\S+)//) { $no_mail{"${1} at ${2}"}++; } elsif($ThisLine =~ /^reading message /) { # ignore } elsif($ThisLine =~ s/^Query status=[23]//) { # ignore. Counted below (Authorization, Connection) } elsif($ThisLine =~ s/^Authorization failure on (\S+)//) { $auth_fail{"${1}"}++; } elsif($ThisLine =~ s/^\S+ connection to \S+ failed: .*//) { # ignore. Counted below } elsif($ThisLine =~ s/^connection to (\S+) \[[^]]+\] failed: (.*).//) { $conn_fail{"${1} -- ${2}"}++; } elsif($ThisLine =~ s/^(\d+) messages? for (\S+) at (\S+).*.//) { $messages_for{"${2} at ${3}"} += $1; } elsif($ThisLine =~ s/^(\d+) messages? \((\d+) seen\) for (\S+) at (\S+).*.//) { $messages_for{"${3} at ${4}"} += ($1-$2); } else { chomp($ThisLine); # Report any unmatched entries... $OtherList{$ThisLine}++; } } if (keys %messages_for) { my $total; print "\nMessages\n"; foreach my $who (sort keys %messages_for) { print " $who: $messages_for{$who}\n"; $total += $messages_for{$who}; } print " Total: $total\n"; } if (keys %conn_fail) { my $total; print "\nConnection failures\n"; foreach my $who (sort keys %conn_fail) { print " $who: $conn_fail{$who} Time(s)\n"; $total += $conn_fail{$who}; } print " Total: $total\n"; } if (keys %auth_fail) { my $total; print "\nAuthorization failures\n"; foreach my $who (sort keys %auth_fail) { print " $who: $auth_fail{$who} Time(s)\n"; $total += $auth_fail{$who}; } print " Total: $total\n"; } if (keys %no_mail) { my $total; print "\nNo Mail\n"; foreach my $who (sort keys %no_mail) { print " $who: $no_mail{$who} Time(s)\n"; $total += $no_mail{$who}; } print " Total: $total\n"; } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$OtherList{$b}<=>$OtherList{$a} } keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/ipop3d0000644000175000017500000000762215075123620020122 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ##################################################### ## Copyright (c) 2008 Pawel Jarosz ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my %Conn_loginok; my %Conn_loginfail; my %Connections; my %OtherList; while (defined(my $ThisLine = )) { chomp($ThisLine); #Solaris ID filter -mgt $ThisLine =~ s/\[ID [0-9]+ [a-z]+\.[a-z]+\] //; # next unless ( $ThisLine=~s/^... .. ..:..:.. [^ ]+ ipop3d\[\d+\]: //); #For testing only next unless (defined($ThisLine)); if ( $ThisLine =~/^Command stream end of file/ ) { next; } if ( $ThisLine =~/^(Autol|L)ogout/ ) { next; } if ( $ThisLine =~/^Trying to get mailbox lock/ ) { next; } if ( $ThisLine =~/^Connection reset by peer/ ) { next; } if ( $ThisLine =~/^Error opening or locking/ ) { next; } if ( $ThisLine =~/^Login failure user=(\S+) host=[\w\. 0-9\-]*\[(\d+.\d+.\d+.\d+)\]/ || $ThisLine =~/^Login failed user=(\S+) auth=\S+ host=[\w\. 0-9\-]*\[(\d+.\d+.\d+.\d+)\]/ || $ThisLine =~/^Login excessive login failures user=(\S+) auth=\S+ host=[\w\. 0-9\-]*\[(\d+.\d+.\d+.\d+)\]/ ) { $Conn_loginfail{$1}{$2}++; next; } if ( $ThisLine =~/service init from (\d+.\d+.\d+.\d+)$/ ) { $Connections{$1}++; next; } if ( $ThisLine =~/^(Login|Auth|APOP|Update) user=(\S+) host=[^\[]*\[(\d+.\d+.\d+.\d+)\]/ ) { $Conn_loginok{$2}{$3}++; next; } if ( $ThisLine =~/^AUTHENTICATE (\S+) failure host=[\w\. 0-9\-]*\[(\d+.\d+.\d+.\d+)\]/ ) { $Conn_loginfail{$1}{$2}++; next; } # Report any unmatched entries... $OtherList{$ThisLine}++; } if ( (keys %Connections) and ($Detail >= 15) ) { print "\nInitialized Connections:\n"; foreach my $ThisOne (sort {$Connections{$b}<=>$Connections{$a}} keys %Connections) { printf " %4i from %s\n" , $Connections{$ThisOne} , $ThisOne; } } if ( (keys %Conn_loginfail) and ($Detail >= 5) ) { print "\nFailed to log in:\n"; foreach my $user (keys %Conn_loginfail) { print "User: $user from:\n"; foreach my $host ( sort keys %{ $Conn_loginfail{$user} } ) { printf " %-35s %4i\n",$host,$Conn_loginfail{$user}{$host}; } } } if ( (keys %Conn_loginok) and ($Detail >=15) ) { print "\nSuccess in log in:\n"; foreach my $user (keys %Conn_loginok) { print "User: $user from:\n"; foreach my $host ( sort keys %{ $Conn_loginok{$user} } ) { printf " %-35s %4i\n",$host,$Conn_loginok{$user}{$host}; } } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; foreach my $line (sort {$OtherList{$b}<=>$OtherList{$a} } keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/sssd0000644000175000017500000001456415075123620017703 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2014 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $IgnoreBackendStatus = $ENV{'ignore_backend_status'} || 0; my $IgnoreEnumerationRequested = $ENV{'ignore_enumeration_requested'} || 0; my $OfflineOkay = $ENV{'offline_okay'} || 0; my %CannotContactKDC; my $Domain; my %Errors; my $Realm; my $Service; my %Starts; my %Stops; my %OtherList; my %BackendStatus; my %BackendOffline; my $EnumerationRequested = 0; my $ignore_p11_child_error = 0; # Lines are of the form: # sssd[service]: while (defined(my $ThisLine = )) { next unless $ThisLine =~ /^sssd/; chomp($ThisLine); # Default service, domain $Service = "Daemon"; $Domain = "unknown"; # Strip off pid: $ThisLine =~ s/\[\d+\]: /: /; # Strip off and record the service if any if ($ThisLine =~ s/^sssd\[([^]]+)(?:\[\d+\])?\]: // or $ThisLine =~ s/^sssd_([^:[]+): //) { $Service = ($1 eq "be") ? "Backend[$Domain]" : $1; } # Strip off and record the backend domain if any if ($ThisLine =~ s/^sssd\[be\[([^]]+)\](?:\[\d+\])?\]: //) { $Service = "Backend[$1]"; $Domain = $1; } # Strip off leading sssd: $ThisLine =~ s/^sssd: //; # Strip off duplicate timestamp if present $ThisLine =~ s/^\(... ... .\d \d\d:\d\d:\d\d \d\d\d\d\) //; # Remove []s from debug messages if any $ThisLine =~ s/^\[(\S+)\] /$1 /; $ThisLine =~ s/^\[(\S+)\] /$1 /; # Remove pids from debug messages if any $ThisLine =~ s/\[\d+\]//; # Ignore debug messages my ($debuglevel) = ($ThisLine =~ /\s\((0x[0-9a-f]{4})\):\s/); next if defined($debuglevel) && hex($debuglevel) > 16; if ($ThisLine =~ /Starting up/) { $Starts{$Service}++; } elsif ($ThisLine =~ /^Shutting down/) { $Stops{$Service}++; } elsif ($ThisLine =~ /error/i) { $Errors{$Service}->{$ThisLine}++; } elsif (my ($status) = ($ThisLine =~ /Backend is (.*)/)) { $BackendStatus{$Domain} = $status; $BackendOffline{$Domain}++ if $BackendStatus{$Domain} eq "offline"; } elsif ($ThisLine =~ /^Enumeration requested but not enabled/) { $EnumerationRequested++ unless $IgnoreEnumerationRequested; } elsif ($Service eq "Daemon" && $ThisLine =~ /exec_child_ex command:/) { # Ignore - https://github.com/SSSD/sssd/issues/7778 } elsif ($Service eq "Daemon" && $ThisLine =~ /Keytab successfully retrieved and stored in:/) { # Ignore } elsif ($Service eq "krb5_child" && ( $ThisLine =~ "Preauthentication failed" or $ThisLine =~ "Client's credentials have been revoked")) { # Ignore - these will generate a pam auth failed message } elsif ($Service eq "p11_child" && $ThisLine =~ /Certificate .* not valid .*Certificate key usage inadequate for attempted operation/) { # sssd ssh does not ignore certificates of different types - ignore the errors generated by it $ignore_p11_child_error = 1; } elsif ($Service eq "p11_child" && $ThisLine =~ /do_work failed/ && $ignore_p11_child_error) { } elsif ($Service eq "p11_child" && $ThisLine =~ /p11_child failed/ && $ignore_p11_child_error) { $ignore_p11_child_error = 0; } elsif (($Realm) = ($ThisLine =~ /Cannot contact any KDC for realm '(.*)'/)) { $CannotContactKDC{$Realm}++ unless $OfflineOkay; } else { $OtherList{$Service}{$ThisLine}++; } } if (keys %Errors) { print "\nSSSD ERRORS:\n"; foreach my $Service (sort {$a cmp $b} keys %Errors) { print " $Service:\n"; foreach my $Error (sort {$a cmp $b} keys %{$Errors{$Service}}) { print " $Error: " . $Errors{$Service}->{$Error} . " Time(s)\n"; } } } # sssd will generally start in offline mode, so don't alert if we've just started up foreach $Domain (keys(%BackendOffline)) { if ($BackendOffline{$Domain} and (($Starts{"Backend[$Domain]"} < $BackendOffline{$Domain}) or ($BackendStatus{$Domain} ne "online")) and not $IgnoreBackendStatus) { print "\nSSSD Backend $Domain went offline $BackendOffline{$Domain} Time(s),"; print " and started " . $Starts{"Backend[$Domain]"} . " Time(s),"; print " last status was $BackendStatus{$Domain}\n"; } } if (keys %CannotContactKDC) { print "\nCannot contact any KDC for realm:\n"; foreach my $Realm (sort {$a cmp $b} keys %CannotContactKDC) { print " $Realm: " . $CannotContactKDC{$Realm} . " Time(s)\n"; } } if (keys %Starts and $Detail) { print "\nSSSD Services Started:\n"; foreach my $Service (sort {$a cmp $b} keys %Starts) { print " $Service: " . $Starts{$Service} . " Time(s)\n"; } } if (keys %Stops and $Detail) { print "\nSSSD Services Stopped:\n"; foreach my $Service (sort {$a cmp $b} keys %Stops) { print " $Service: " . $Stops{$Service} . " Time(s)\n"; } } if ($EnumerationRequested) { print "\nEnumeration requested but not enabled: $EnumerationRequested Time(s)\n"; } if (keys %OtherList) { print "\n\n**Unmatched Entries**\n"; foreach my $service (sort {$a cmp $b} keys %OtherList) { print " $service:\n"; foreach my $line (sort {$a cmp $b} keys %{$OtherList{$service}}) { print " $line: $OtherList{$service}{$line} Time(s)\n"; } } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/zz-sys0000644000175000017500000000444415075123620020202 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ####################################################### ## Copyright (c) 2008 Laurent Dufour ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use POSIX qw(uname); my %CPUModel; my $Model; my ($OSname, $hostname, $release, $version, $machine) = POSIX::uname(); print " Machine: $machine\n"; my $OStitle; $OStitle = $OSname; $OStitle = "Solaris" if ($OSname eq "SunOS" && $release >= 2); print (" Release: $OStitle $release\n"); if (open FH, '<', '/proc/cpuinfo') { while () { if (($Model) = $_ =~ /^model name\s*: (.*)$/) { $CPUModel{$Model}++; } } close(FH); } if (keys %CPUModel) { print ("\n CPU Model(s):"); foreach my $cpu (keys %CPUModel) { print ("\n CPU Model: $cpu: $CPUModel{$cpu} processors"); } } print ("\n\n"); if (open FH, '<', '/proc/meminfo') { while (my $ThisLine = ) { if ($ThisLine =~ /^(Mem|Swap)(Total|Free)/) { chomp ($ThisLine); print (" " . $ThisLine); my @fields = split(' ', $ThisLine); printf (" (%.2f GB)\n", @fields[1]/(1024*1024)); } } close(FH); } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/http0000644000175000017500000006130715075123620017703 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ##################################################### # Copyright (c) 2008 Michael Romeo # Covered under the included MIT/X-Consortium License: # http://www.opensource.org/licenses/mit-license.php # All modifications and contributions by other persons to # this script are assumed to have been donated to the # Logwatch project and thus assume the above copyright # and licensing terms. If you want to make contributions # under your own copyright or a different license this # must be explicitly stated in the contribution an the # Logwatch project reserves the right to not accept such # contributions. If you have made significant # contributions to this script and want to claim # copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################## #use diagnostics; use strict; use Logwatch ':sort'; # use re "debug"; # # parse httpd access_log # # Get the detail level and # Build tables of the log format to parse it and determine whats what # my $detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $ignoreURLs = $ENV{'http_ignore_urls'}; my $ignoreIPs = $ENV{'http_ignore_ips'}; my $ignoreEval = $ENV{'http_ignore_eval'}; my $ignore_error_hacks = $ENV{'http_ignore_error_hacks'} || 0; my $user_display = $ENV{'http_user_display'}; my $logformat = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"|%h %l %u %t \"%r\" %>s %b|%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"; if (defined $ENV{'logformat'}) { $logformat = $ENV{'logformat'}; } my @log_fields = (); my @log_format = (); if ((defined $ENV{'http_fields'}) and (defined $ENV{'http_format'})) { @log_fields = split(" ", $ENV{'http_fields'}); @log_format = split(" ", $ENV{'http_format'}); } # # Initialization etc. # my $byte_summary = 0; my $failed_requests = 0; my %field = (); my %hacks =(); my %hack_success =(); my %needs_exam =(); my %users_logged =(); my %ban_ip =(); my %robots =(); my $pattern = ""; my $isahack = 0; my $a5xx_resp = 0; my $a4xx_resp = 0; my $a3xx_resp = 0; my $a2xx_resp = 0; my $a1xx_resp = 0; my $image_count = 0; my $image_bytes = 0; my $docs_count = 0; my $docs_bytes = 0; my $archive_count = 0; my $archive_bytes = 0; my $sound_count = 0; my $sound_bytes = 0; my $movie_count = 0; my $movie_bytes = 0; my $winexec_count = 0; my $winexec_bytes = 0; my $content_count = 0; my $content_bytes = 0; my $redirect_count = 0; my $redirect_bytes = 0; my $other_count = 0; my $other_bytes = 0; my $total_hack_count = 0; my $wpad_count = 0; my $wpad_bytes = 0; my $src_count = 0; my $src_bytes = 0; my $logs_count = 0; my $logs_bytes = 0; my $images_count = 0; my $images_bytes = 0; my $fonts_count = 0; my $fonts_bytes = 0; my $config_count = 0; my $config_bytes = 0; my $xpcomext_count = 0; my $xpcomext_bytes = 0; my $mozext_count = 0; my $mozext_bytes = 0; my $proxy_count = 0; my $proxy_bytes = 0; my %proxy_host = (); my $host = ""; my $notparsed = ""; my $notparsed_count =0; ###################### # file type comparisons are case-insensitive my $image_types = '(\.bmp|\.cdr|\.emz|\.gif|\.ico|\.jpe?g|\.png|\.svg|\.sxd|\.tiff?|\.wbmp|\.webp|\.wmf|\.wmz|\.xdm)'; my $content_types = '('; $content_types = $content_types.'\/server-status|\/server-info'; $content_types = $content_types.'|\.htm|\.html|\.jhtml|\.phtml|\.shtml|\/\.?'; $content_types = $content_types.'|\.html\.[a-z]{2,3}(_[A-Z]{2,3})?'; $content_types = $content_types.'|\.inc|\.php|\.php3|\.asmx|\.asp|\.pl|\.wml'; $content_types = $content_types.'|^\/mailman\/.*'; $content_types = $content_types.'|\/sqwebmail.*'; $content_types = $content_types.'|^\/announce|^\/scrape'; # BitTorrent tracker mod_bt $content_types = $content_types.'|\.torrent'; $content_types = $content_types.'|\.css|\.js|\.cgi'; $content_types = $content_types.'|\.fla|\.swf|\.rdf'; $content_types = $content_types.'|\.class|\.jsp|\.jar|\.java'; $content_types = $content_types.'|COPYRIGHT|README|FAQ|INSTALL|\.txt)'; my $docs_types = '(\.asc|\.bib|\.djvu|\.docx?|\.dot|\.dtd|\.dvi|\.gnumeric|\.mcd|\.mso|\.pdf|\.pps|\.pptx?|\.ps|\.rtf|\.sxi|\.tex|\.text|\.tm|\.xlsx?|\.xml)'; my $archive_types = '(\.7z|\.ace|\.bz2|\.cab|\.deb|\.dsc|\.ed2k|\.gz|\.hqx|\.md5|\.rar|\.rpm|\.sig|\.sign|\.tar|\.tbz2|\.tgz|\.vl2|\.z|\.zip|\.hdr)'; my $sound_types = '(\.aac|\.au|\.aud|\.m4a|\.mid|\.mp3|\.oga|\.pls|\.ram|\.raw|\.rm|\.wav|\.wma|\.xsm)'; my $movie_types = '(\.asf|\.ass|\.avi|\.idx|\.flv|\.m2?ts|\.mkv|\.mp4|\.mpe?g|\.mov|\.ogg|\.ogv|\.qt|\.psb|\.srt|\.ssa|\.smi|\.sub|\.webm|\.wmv)'; my $winexec_types = '(\.bat|\.com|\.exe|\.dll)'; my $wpad_files = '(wpad\.dat|wspad\.dat|proxy\.pac)'; my $program_src = '('; $program_src = $program_src.'\.bas|\.cs?|\.cpp|\.diff|\.f|\.h|\.init|\.m|\.mo|\.pas|\.patch|\.po|\.pot|\.py|\.sh|\.spec'; $program_src = $program_src.'|Makefile|Makefile_c|Makefile_f77)'; my $images_types = '(\.bin|\.cue|\.img|\.iso|\.run)'; my $logs_types = '(\.log|_log|-log|\.logs|\.out|\.wyniki)'; my $fonts_types = '(\.aft|\.otf|\.ttf|\.woff)'; my $config_types = '(\.cfg|\.conf|\.config|\.ini|\.properties)'; my $xpcomext_types = '(\.xpt)'; my $mozext_types = '(\.xul)'; # HTTP Status codes from HTTP/Status.pm, to avoid loading package # that may or may not exist. We only need those >=400, but all # are included for potential future use. Those labeled with the # NGINX comment are NGINX-specific only, and not approved by IANA. # They need to be changed if a future conflict arises in the code's # definition. my %StatusCode = ( 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 103 => 'Early Hints', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Request Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Too Early', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 444 => 'Closed Without Response', # NGINX 451 => 'Unavailable for Legal Reasons', 494 => 'Request Header Too Large', # NGINX 495 => 'Cert Error', # NGINX 496 => 'No Cert', # NGINX 497 => 'To HTTPS', # NGINX 499 => 'Client Closed Request', # NGINX 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required' ); # # what to look for as an attack USE LOWER CASE!!!!!! # my @exploits = ( '/\.\./\.\./\.\./', '\.\./\.\./config\.sys', '/\.\./\.\./\.\./autoexec\.bat', '/\.\./\.\./windows/user\.dat', '\\\x02\\\xb1', '\\\x04\\\x01', '\\\x05\\\x01', '\\\x90\\\x02\\\xb1\\\x02\\\xb1', '\\\x90\\\x90\\\x90\\\x90', '\\\xff\\\xff\\\xff\\\xff', '\\\xe1\\\xcd\\\x80', '\\\xff\xe0\\\xe8\\\xf8\\\xff\\\xff\\\xff-m', '\\\xc7f\\\x0c', '\\\x84o\\\x01', '\\\x81', '\\\xff\\\xe0\\\xe8', '\/c\+dir', '\/c\+dir\+c', '\.htpasswd', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'author\.exe', 'boot\.ini', 'cmd\.exe', 'c%20dir%20c', 'default\.ida', 'fp30reg\.dll', 'httpodbc\.dll', 'nsiislog\.dll', 'passwd$', 'root\.exe', 'shtml\.exe', 'win\.ini', 'xxxxxxxxxxxxxxxxxxxxxx', ); # # Define some useful RE patterns # my %re_pattern = ( space => '(.*)', brace => '\[(.*)\]', quote => '\"(.*)\"'); # # Build the regex to parse the line # for (my $i = 0; $i < @log_format; $i++) { $pattern = $pattern.$re_pattern{$log_format[$i]}.'\\s'; } # this is easier than coding last element logic in the loop chop($pattern); chop($pattern); # The following are used to build up pattern matching strings for # the log format used in the access_log files. my @parse_string = (); my @parse_field = (); my $parse_index = 0; my $parse_subindex = 0; $parse_string[$parse_index] = ""; $parse_field[$parse_index] = (); if ($pattern) { # accommodate usage of HTTP_FIELDS and HTTP_FORMAT $parse_string[0] = $pattern; $parse_field[0] = [@log_fields]; $parse_index++; } $parse_string[$parse_index] = ""; $parse_field[$parse_index] = (); my $end_loop = 1; $logformat =~ s/%[\d,!]*/%/g; while ($end_loop) { if ($logformat =~ /\G%h/gc) { $parse_string[$parse_index] .= "(\\S*?)"; $parse_field[$parse_index][$parse_subindex++] = "client_ip"; } elsif ($logformat =~ /\G%l/gc) { $parse_string[$parse_index] .= "(\\S*?)"; $parse_field[$parse_index][$parse_subindex++] = "ident"; } elsif ($logformat =~ /\G%u/gc) { $parse_string[$parse_index] .= "(\\S*?)"; $parse_field[$parse_index][$parse_subindex++] = "userid"; } elsif ($logformat =~ /\G%t/gc) { $parse_string[$parse_index] .= "(\\[.*\\])"; $parse_field[$parse_index][$parse_subindex++] = "timestamp"; } elsif ($logformat =~ /\G%r/gc) { $parse_string[$parse_index] .= "(.*)"; $parse_field[$parse_index][$parse_subindex++] = "request"; } elsif ($logformat =~ /\G%>?s/gc) { $parse_string[$parse_index] .= "(\\d{3})"; $parse_field[$parse_index][$parse_subindex++] = "http_rc"; } elsif ($logformat =~ /\G%b/gc) { # "transfered" is misspelled, but not corrected because this string # comes from the configuration file, and would create a compatibility # issue $parse_field[$parse_index][$parse_subindex++] = "bytes_transfered"; $parse_string[$parse_index] .= "(-|\\d*)"; } elsif ($logformat =~ /\G%V/gc) { $parse_string[$parse_index] .= "(\\S*?)"; $parse_field[$parse_index][$parse_subindex++] = "server_name"; } elsif ($logformat =~ /\G%I/gc) { $parse_field[$parse_index][$parse_subindex++] = "bytes_in"; $parse_string[$parse_index] .= "(-|\\d*)"; } elsif ($logformat =~ /\G%O/gc) { $parse_field[$parse_index][$parse_subindex++] = "bytes_out"; $parse_string[$parse_index] .= "(-|\\d*)"; } elsif ($logformat =~ /\G%\{Referer}i/gci) { $parse_string[$parse_index] .= "(.*)"; $parse_field[$parse_index][$parse_subindex++] = "referrer"; } elsif ($logformat =~ /\G%\{User-Agent}i/gci) { $parse_string[$parse_index] .= "(.*)"; $parse_field[$parse_index][$parse_subindex++] = "agent"; } elsif ($logformat =~ /\G%(\{.*?\})?./gc) { $parse_string[$parse_index] .= "(.*?)"; $parse_field[$parse_index][$parse_subindex++] = "not_used"; } elsif ($logformat =~ /\G\|/gc) { $parse_index++; $parse_subindex = 0; $parse_string[$parse_index] = ""; $parse_field[$parse_index] = (); # perl 5.6 does not detect end of string properly in next elsif block, # so we test it explicitly here } elsif ($logformat =~ /\G$/gc) { $end_loop = 0; } elsif ((my $filler) = ($logformat =~ /\G([^%\|]*)/gc)) { $parse_string[$parse_index] .= $filler; # perl 5.6 loses track of match position, so we force it. Perl 5.8 # and later does it correctly, so it was fixed in 5.7 development. if ($] < 5.007) {pos($logformat) += length($filler);} } else { $end_loop = 0; } } ################# print "RE pattern = $pattern \n"; # # Process log file on stdin # while (my $line = ) { chomp($line); ################## print "Line = $line \n"; # # parse the line per the input spec # my @parsed_line; for $parse_index (0..$#parse_string) { if (@parsed_line = $line =~ /$parse_string[$parse_index]/) { @log_fields = @{$parse_field[$parse_index]}; last; } } if (not @parsed_line) { $notparsed_count++; if ($notparsed_count <= 10) { $notparsed = $notparsed . " " . $line . "\n"; } next; } # http_rc checked even if not set, so we set a default $field{http_rc} = 0; # hash the results so we can identify the fields # for my $i (0..$#log_fields) { # print "$i $log_fields[$i] $parsed_line[$i] \n"; $field{$log_fields[$i]} = $parsed_line[$i]; } ## ## Do the default stuff ## # # Break up the request into method, url and protocol # ($field{method},$field{url},$field{protocol}) = split(/ +/,$field{"request"}); if (! $field{url}) { $field{url}='null'; } $field{lc_url} = lc $field{url}; # # Bytes sent Summary # Apache uses "-" to represent 0 bytes transferred # if ($field{bytes_transfered} eq "-") {$field{bytes_transfered} = 0}; $byte_summary += $field{bytes_transfered}; # # loop to check for typical exploit attempts # if (!$ignore_error_hacks) { for (my $i = 0; $i < @exploits; $i++) { # print "$i $exploits[$i] $field{lc_url} \n"; if ( ($field{lc_url} =~ /$exploits[$i]/i) && !((defined $ignoreURLs) && ($field{url} =~ /$ignoreURLs/i)) && !((defined $ignoreIPs) && ($field{client_ip} =~ /$ignoreIPs/)) ) { $hacks{$field{client_ip}}{$exploits[$i]}++; $total_hack_count += 1; $ban_ip{$field{client_ip}} = " "; if ($field{http_rc} < 300) { $hack_success{$field{url}} = $field{http_rc}; } } } } # # Count types and bytes # # this is only printed if detail > 4 but it also looks # for 'strange' stuff so it needs to run always # ($field{base_url},$field{url_parms}) = split(/\?/,$field{"lc_url"}); if ($field{base_url} =~ /$image_types$/oi) { $image_count += 1; $image_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$docs_types$/oi) { $docs_count += 1; $docs_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$archive_types$/oi) { $archive_count += 1; $archive_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$sound_types$/oi) { $sound_count += 1; $sound_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$movie_types$/oi) { $movie_count += 1; $movie_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$winexec_types$/oi) { $winexec_count += 1; $winexec_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$content_types$/oi) { $content_count += 1; $content_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$wpad_files$/oi) { $wpad_count += 1; $wpad_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$program_src$/oi) { $src_count += 1; $src_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$images_types$/oi) { $images_count += 1; $images_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$logs_types$/oi) { $logs_count += 1; $logs_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$fonts_types$/oi) { $fonts_count += 1; $fonts_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$config_types$/oi) { $config_count += 1; $config_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$xpcomext_types$/oi) { $xpcomext_count += 1; $xpcomext_bytes += $field{bytes_transfered}; } elsif ($field{base_url} =~ /$mozext_types$/oi) { $mozext_count += 1; $mozext_bytes += $field{bytes_transfered}; } elsif ($field{http_rc} =~ /3\d\d/) { $redirect_count += 1; $redirect_bytes += $field{bytes_transfered}; } elsif ($field{method} =~ /CONNECT/) { $proxy_count += 1; $proxy_bytes += $field{bytes_transfered}; $proxy_host{"$field{client_ip} -> $field{base_url}"}++; } else { $other_count += 1; $other_bytes += $field{bytes_transfered}; } if ( ($field{http_rc} >= 400) && !shouldIgnore("needs_exam") ) { my $fmt_url = $field{url}; if (length($field{url}) > 60) { $fmt_url = substr($field{url},0,42) . " ... " . substr($field{url},-15,15); } $needs_exam{$field{http_rc}}{$fmt_url}++; } if (defined $field{userid} && $field{userid} ne "-" && (eval $user_display) && !shouldIgnore("users_logged") ) { $users_logged{$field{userid}}{$field{client_ip}}++; } ## ## Do the > 4 stuff ## # # Response Summary # if ($field{http_rc} > 499 ) { $a5xx_resp += 1; } elsif ($field{http_rc} > 399 ) { $a4xx_resp += 1; } elsif($field{http_rc} > 299 ) { $a3xx_resp += 1; } elsif($field{http_rc} > 199 ) { $a2xx_resp += 1; } else { $a1xx_resp += 1; } # # Count the robots who actually ask for the robots.txt file # if ($field{lc_url} =~ /^\/robots.txt$/) { if (defined $field{agent}) { $robots{$field{agent}} +=1; } } } ## End of while loop ############################################# ## output the results ## if ($detail >4) { printf "%.2f MB transferred " , $byte_summary/(1024*1024); print "in "; print my $resp_total = ($a1xx_resp + $a2xx_resp + $a3xx_resp + $a4xx_resp + $a5xx_resp); print " responses "; print " (1xx $a1xx_resp, 2xx $a2xx_resp, 3xx $a3xx_resp,"; print " 4xx $a4xx_resp, 5xx $a5xx_resp) \n"; my $lr = length($resp_total); if ($image_count > 0) { printf " %*d Images (%.2f MB),\n" , $lr, $image_count, $image_bytes/(1024*1024); } if ($docs_count > 0) { printf " %*d Documents (%.2f MB),\n" , $lr, $docs_count, $docs_bytes/(1024*1024); } if ($archive_count > 0) { printf " %*d Archives (%.2f MB),\n" , $lr, $archive_count, $archive_bytes/(1024*1024); } if ($sound_count > 0) { printf " %*d Sound files (%.2f MB),\n" , $lr, $sound_count, $sound_bytes/(1024*1024); } if ($movie_count > 0) { printf " %*d Movies files (%.2f MB),\n" , $lr, $movie_count, $movie_bytes/(1024*1024); } if ($winexec_count > 0) { printf " %*d Windows executable files (%.2f MB),\n" , $lr, $winexec_count, $winexec_bytes/(1024*1024); } if ($content_count > 0) { printf " %*d Content pages (%.2f MB),\n" , $lr, $content_count, $content_bytes/(1024*1024); } if ($redirect_count > 0) { printf " %*d Redirects (%.2f MB),\n" , $lr, $redirect_count, $redirect_bytes/(1024*1024); } if ($wpad_count > 0) { printf " %*d Proxy Configuration Files (%.2f MB),\n" , $lr, $wpad_count, $wpad_bytes/(1024*1024); } if ($src_count > 0) { printf " %*d Program source files (%.2f MB),\n" , $lr, $src_count, $src_bytes/(1024*1024); } if ($images_count > 0) { printf " %*d CD Images (%.2f MB),\n" , $lr, $images_count, $images_bytes/(1024*1024); } if ($logs_count > 0) { printf " %*d Various Logs (%.2f MB),\n" , $lr, $logs_count, $logs_bytes/(1024*1024); } if ($fonts_count > 0) { printf " %*d Fonts (%.2f MB),\n" , $lr, $fonts_count, $fonts_bytes/(1024*1024); } if ($config_count > 0) { printf " %*d Configs (%.2f MB),\n" , $lr, $config_count, $config_bytes/(1024*1024); } if ($xpcomext_count > 0) { printf " %*d XPCOM Type Libraries (%.2f MB),\n" , $lr, $xpcomext_count, $xpcomext_bytes/(1024*1024); } if ($mozext_count > 0) { printf " %*d Mozilla extensions (%.2f MB),\n" , $lr, $mozext_count, $mozext_bytes/(1024*1024); } if ($proxy_count > 0) { printf " %*d mod_proxy requests (%.2f MB),\n" , $lr, $proxy_count, $proxy_bytes/(1024*1024); } if ($other_count > 0) { printf " %*d Other (%.2f MB) \n" , $lr, $other_count, $other_bytes/(1024*1024); } } # # List attempted exploits # if (($detail >4) and $total_hack_count) { print "\nAttempts to use known hacks by ".(keys %hacks). " hosts were logged $total_hack_count time(s) from:\n"; my $order = TotalCountOrder(%hacks); foreach my $i (sort $order keys %hacks) { my $hacks_per_ip = 0; foreach my $j ( keys %{$hacks{$i}} ) { $hacks_per_ip += $hacks{$i}{$j}; } print " $i: $hacks_per_ip Time(s)\n"; if ($detail > 9) { foreach my $j ( keys %{$hacks{$i}} ) { print " $j $hacks{$i}{$j} Time(s) \n"; } } } } if (keys %proxy_host) { print "\nConnection attempts using mod_proxy:\n"; foreach $host (sort {$a cmp $b} keys %proxy_host) { print " $host: $proxy_host{$host} Time(s)\n"; } } # # List (wannabe) blackhat sites # if (keys %ban_ip and $detail) { print "\nA total of ".scalar(keys %ban_ip)." sites probed the server \n"; if ($detail > 4) { foreach my $i (sort keys %ban_ip) { print " $i\n"; } } } # # List possible successful probes # if (keys %hack_success) { print "\nA total of " . scalar(keys %hack_success) . " possible successful probes were detected (the following URLs\n"; print "contain strings that match one or more of a listing of strings that\n"; print "indicate a possible exploit):\n\n"; foreach my $i (keys %hack_success) { print " $i HTTP Response $hack_success{$i} \n"; } } # # List error response codes # if (keys %needs_exam and ($detail or $a5xx_resp)) { print "\nRequests with error response codes\n"; for my $code (sort keys %needs_exam) { if (not defined $StatusCode{$code}) { $StatusCode{$code} = "\(undefined\)"; } if ($detail || ($code >= 500) || (($ENV{"http_rc_detail_rep_$code"} || $detail) < $detail)) { if ( ($ENV{"http_rc_detail_rep_$code"} || $detail) > $detail ) { # only display summary for this code my $t = 0; my $u = 0; foreach my $k ( keys %{$needs_exam{$code}}) { $u += 1; $t += $needs_exam{$code}{$k}; } print " $code $StatusCode{$code} SUMMARY - $u URLs, total: $t Time(s)\n"; } else { print " $code $StatusCode{$code}\n"; for my $url (sort { ($needs_exam{$code}{$b} <=> $needs_exam{$code}{$a}) or ($a cmp $b) } keys %{$needs_exam{$code}}) { print " $url: $needs_exam{$code}{$url} Time(s)\n"; } } } } } if (keys %users_logged) { print "\nUsers logged successfully\n"; for my $user (sort keys %users_logged) { my %userips = %{$users_logged{$user}}; # If one user name logged from many IPs, don't print them all. 5 is arbitrary if (scalar(keys %userips) > 5) { my $count = 0; for my $ip (keys %userips) { $count += $userips{$ip}; } print " $user: $count Time(s) from ".scalar(keys %userips)." addresses\n"; } else { print " $user\n"; for my $ip (sort keys %userips) { print " $ip: $userips{$ip} Time(s)\n"; } } } } # # List robots that identified themselves # if (keys %robots and ($detail > 4)) { print "\nA total of ".scalar(keys %robots)." ROBOTS were logged \n"; foreach my $i (keys %robots) { if ($detail > 9) { print " $i $robots{$i} Time(s) \n"; } } } if ($notparsed) { print "\nThis is a listing of log lines that were not parsed correctly.\n"; print "Perhaps the variable \$LogFormat in file conf/services/http.conf\n"; print "is not correct?\n\n"; if ($notparsed_count > 10) { print "(Only the first ten are printed; there were a total of $notparsed_count)\n"; } print $notparsed; } exit (0); sub shouldIgnore { my($context)=@_; if( ((defined $ignoreURLs) && ($field{url} =~ /$ignoreURLs/i)) || ((defined $ignoreIPs) && ($field{client_ip} =~ /$ignoreIPs/)) ) { return 1; } return (eval $ignoreEval); } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/pluto0000644000175000017500000003032115075123620020057 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # Note (8/28/2005, BL): # # This script was apparently written for FreeS/WAN, which is no longer # supported (see http://www.freeswan.org). But it also appears to work # with Openswan (http://www.openswan.org), which is described as a code # fork of FreeS/WAN. # # Also, notice that in this script, many variables are set, but not # printed. And many logged statements are filtered by this script. # # So this script would probably benefit from an update to clean it up # and ensure full compatibility with the newer Openswan. ########################################################################## # This is a scanner for logwatch that processes FreeSWAN's # Pluto log files and attempts to # make some sense out of them. # # Please CC suggestions to mcr@freeswan.org and/or design@lists.freeswan.org # the vendorID hash maps vendor IDs to products. VendorIDs are hashs of # internal stuff from each vendor. Grow this table as you encounter new # products. ####################################################### ## Copyright (c) 2008 Kirk Bauer ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; #Init String Container my ( $baddelete, $ipaddr, $ipport, $lastattempt, $oldinfo, $phase2, $rest, $stateinfo, $today, $vid ); #Init Hashes my ( %badexch, %conns, %crlUpdate, %crlUpdateSince, %ipsecSAs, %loglines, %maxattempts, %peerID, %peerIP, %quickmode, %rekeyfail, %rekeyfailI1, %rekeyfailQI1, %rekeyfailQR1, %rekeyfailR1, %rekeyfail_ICMPunreachable, %rekeyfail_notknown, %rekeysuccess, %relevantlog, %setupfail, %statechain, %xauthsuccess, %vendorID ); my $debug=0; $vendorID{"p....}..&..i...5..............................."}="KAME/Racoon"; while(<>) { # May 4 04:04:33 abigail Pluto[24170]: "abigail-istari" #1479: ISAKMP SA expired (LATEST!) chop; my ($month,$day,undef,undef,$process,$conn,$msg)=split(/ +/,$_,7); $today="$month $day"; next unless ($process =~ /pluto/i); my $iserror=0; if ($conn eq "ERROR:") { $iserror = 1; (undef,$conn,$msg)=split(/ +/,$msg,3); } $loglines{$today}++; print STDERR "Msg: $msg\n" if $debug>1; if($msg =~ /([^\#]*)\#(\d*)\:(.*)/) { $ipaddr = $1; $stateinfo = $2; $rest = $3; } elsif($msg =~ /no Phase 1 state for Delete/) { # baddelete not currently used #$baddelete++; next; } elsif($msg =~ /from ([^:]*)\:([^:]*)\: Main Mode message is part of an unknown exchange/) { $ipaddr = $1; $ipport = $2; $badexch{"[$ipaddr]:$ipport"}++; next; } else { print STDERR "Failed to decode: $msg (of $_)\n" if $debug; next; } # print STDERR "conn: $conn IP: $ipaddr STATE: $stateinfo\n" if $debug; $conn =~ s/\"(.*)\"/$1/; $conn =~ s/\[\d\]$//; $conns{$conn}++; if(!defined($peerIP{"$conn|$ipaddr"})) { #print STDERR "Adding $ipaddr to $conn\n" if $debug; $peerIP{$conn}=$peerIP{$conn}.$ipaddr." "; } $peerIP{"$conn|$ipaddr"}++; # This code no longer seems to be active #$stateobjects{$stateinfo}++; #if(!defined($peer{$stateinfo}) && length($ipaddr)>0) { # $peer{$stateinfo}=$ipaddr; #} # ignore following next if($rest =~ /ISAKMP SA expired/); next if($rest =~ /responding to Main Mode/); next if($rest =~ /responding to Quick Mode/); next if($rest =~ /IPsec SA expired/); next if($rest =~ /ignoring informational payload, type IPSEC_INITIAL_CONTACT/); next if($rest =~ /regenerating DH private secret to avoid Pluto 1.0 bug handling public value with leading zero/); next if($rest =~ /regenerating DH private secret to avoid Pluto 1.0 bug handling shared secret with leading zero/); next if($rest =~ /shared DH secret has leading zero -- triggers Pluto 1.0 bug/); next if($rest =~ /(received|ignoring) Delete SA(|\(0x.*\)) payload/); next if($rest =~ /received and ignored informational message/); next if($rest =~ /discarding duplicate packet; already STATE_MAIN_../); next if($rest =~ /discarding duplicate packet; already STATE_QUICK_../); next if($rest =~ /deleting state \(STATE_MAIN_..\)/); next if($rest =~ /deleting state \(STATE_QUICK_..\)/); next if($rest =~ /Quick Mode .. message is unacceptable because it uses a previously used Message ID/); next if($rest =~ /deleting connection .* instance with peer .*/); next if($rest =~ /dropping and reinitiating exchange to avoid Pluto 1.0 bug handling DH shared secret with leading zero byte/); next if($rest =~ /KE has 191 byte DH public value; 192 required/); next if($rest =~ /retransmitting in response to duplicate packet; already STATE_MAIN_../); next if($rest =~ /(Main mode p|P)eer ID is /); next if($rest =~ /transition from state .* to state/); next if($rest =~ /NAT-Traversal: Result using/); next if($rest =~ /no crl from issuer/); next if($rest =~ /I am sending (a certificate request|my cert)/); next if($rest =~ /no suitable connection for peer/); next if($rest =~ /sending encrypted notification/); next if($rest =~ /enabling possible NAT-traversal with method/); next if($rest =~ /(received|ignoring) Vendor ID payload/); next if($rest =~ /ignoring unknown Vendor ID payload/); next if($rest =~ /Dead Peer Detection \(RFC 3706\): enabled/); next if($rest =~ /DPD: No response from peer - declaring peer dead/); next if($rest =~ /DPD Error: could not find newest phase 1 state/); next if($rest =~ /Informational Exchange message is invalid because it has a previously used Message ID/); next if($rest =~ /discarding packet received during asynchronous work \(DNS or crypto\) in STATE_(MAIN|QUICK)_../); next if($rest =~ /STATE_(MAIN|QUICK)_[RI][1-3]: sent [MQ][RI][1-3], expecting [MQ][IR][1-3]/); next if($rest =~ /STATE_QUICK_R1: sent QR1, inbound IPsec SA installed, expecting QI2/); next if($rest =~ /down-client output/); next if($rest =~ /(restore|update)resolvconf-client output/); next if($rest =~ /transform .* ignored/); next if($rest =~ /multiple DH groups were set in aggressive mode\./); next if($rest =~ /received mode cfg reply/); next if($rest =~ /modecfg: Sending IP request/); next if($rest =~ /setting .* address to/); next if($rest =~ /STATE_XAUTH_I1: XAUTH client - awaiting CFG_set/); next if($rest =~ /initiating Aggressive Mode/); next if($rest =~ /Aggressive mode peer ID is/); next if($rest =~ /protocol\/port in Phase \d ID Payload must be/); next if($rest =~ /XAUTH: Bad Message: /); next if($rest =~ /XAUTH: Answering XAUTH challenge with user/); next if($rest =~ /Received IP4|DNS|subnet /); next if($rest =~ /sendto on .* to .* failed in delete notify/); $relevantlog{"$today"}++; print STDERR "Rest is $rest\n" if $debug>1; # but process these. if($rest =~ /initiating Main Mode to replace \#(.*)/) { $oldinfo = $1; $statechain{$conn.$stateinfo}="$conn|$oldinfo"; next; } elsif($rest =~ /initiating Main Mode/) { $statechain{$conn.$stateinfo}="$conn"; next; } elsif($rest =~ /initiating Quick Mode (.*) to replace \#(.*)/) { $oldinfo = $2; $phase2 = $1; $statechain{"$conn|$stateinfo"}="$conn|$oldinfo"; $quickmode{"$conn"}=$quickmode{"$conn"}." ".$phase2; next; } elsif($rest =~ /initiating Quick Mode (.*)/) { $phase2 = $1; $statechain{"$conn|$stateinfo"}="$conn"; $quickmode{"$conn"}=$quickmode{"$conn"}." ".$phase2; next; } elsif($rest =~ /ISAKMP SA established/) { $rekeysuccess{$conn}++; next; } elsif($rest =~ /cannot respond to IPsec SA request because no connection is known for (.*)/) { $rekeyfail{$conn}++; #$rekeyfail_notknown{$1}++; } elsif($rest =~ /crl update is overdue since (.*)/) { $crlUpdate{$conn}++; $crlUpdateSince{$conn} = $1; next; } elsif($rest =~ /max number of retransmissions \((.*)\) reached STATE_QUICK_I./) { $rekeyfail{$conn}++; #$rekeyfailQI1{$conn}++; next; } elsif($rest =~ /max number of retransmissions \((.*)\) reached STATE_QUICK_R./) { $rekeyfail{$conn}++; #$rekeyfailQR1{$conn}++; next; } elsif($rest =~ /max number of retransmissions \((.*)\) reached STATE_MAIN_I./) { $rekeyfail{$conn}++; #$rekeyfailI1{$conn}++; next; } elsif($rest =~ /max number of retransmissions \((.*)\) reached STATE_MAIN_R./) { $rekeyfail{$conn}++; #$rekeyfailR1{$conn}++; next; } elsif($rest =~ /ERROR: asynchronous network error report on .* for message to .* port 500, complainant .*:.*errno (.*), origin ICMP type (.*) code (.*)/) { $rekeyfail{$conn}++; #$rekeyfail_ICMPunreachable{$conn}++; } elsif($rest =~ /ERROR: asynchronous network error report on .* for message to .* port 500, complainant .*:.*errno (.*), origin ICMP type (.*) code (.*)/) { $rekeyfail{$conn}++; #$rekeyfail_ICMPunreachable{$conn}++; } elsif($rest =~ /XAUTH: Successfully Authenticated/) { $xauthsuccess{$conn}++; } elsif($rest =~ /starting keying attempt (.*) of an unlimited number/) { $lastattempt=$1; if($maxattempts{$conn} < $lastattempt) { $maxattempts{$conn} = $lastattempt; } next; } elsif($rest =~ /Vendor ID: (.*)/) { $vid=$1; if(defined($vendorID{$vid})) { $peerID{$conn}=$vendorID{$vid}; } else { $peerID{$conn}="unknown $vid"; $vendorID{$vid}="unknown $vid at $stateinfo/$ipaddr\n"; } next; } elsif($rest =~ /prepare-client output.*/) { $setupfail{$conn}++; } elsif(($rest =~ /sent QI2, IPsec SA established/) || ($rest =~ /IPsec SA established/)) { $ipsecSAs{$conn}++; next; } else { print STDERR "UNKNOWN: $_"."\n"; } } if (keys %loglines) { print "Overview summary of log files:\n"; foreach my $day (keys %loglines) { print "\t $day had ".$loglines{$day}." entries of which ".$relevantlog{$day}." were relevant\n"; } } if (keys %conns) { print "Summary by peer:\n"; foreach my $conn (keys %conns) { print " Peer $conn caused $conns{$conn} lines of output.\n"; print "\tconnected from:".$peerIP{$conn}."\n"; if(defined($peerID{$conn})) { print "\tVID: ".$peerID{$conn}."\n"; } print "\tKeyed: ".($rekeysuccess{$conn}+0)." successes ",($rekeyfail{$conn}+0)." failures (max retries: ".($maxattempts{$conn}+0).")\n"; print "\tIPsec SAs: ".($ipsecSAs{$conn}+0)."\n"; if($setupfail{$conn} > 0) { print "\tSetup failures: ".$setupfail{$conn}."\n"; } if($xauthsuccess{$conn} > 0) { print "\tXAUTH successful connections: ".$xauthsuccess{$conn}."\n"; } if($crlUpdate{$conn} > 0) { print "\tOverdue CRL update since: ".$crlUpdateSince{$conn}." (".$crlUpdate{$conn}." times)\n"; } } } if (keys %badexch) { print "Summary of bad peers\n"; foreach my $badpeer (keys %badexch) { print "\t".$badpeer." caused ".$badexch{$badpeer}." bad exchanges\n"; } } # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/barracuda0000644000175000017500000003132615075123620020646 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # # Barracuda Spam Firewall parser # Written by Hugo van der Kooij (HvdK) # Based on existing code of logwatch and the documentation on: # http://www.barracudanetworks.com/ns/downloads/BarracudaSyslog.pdf # ######################################################## ## Copyright (c) 2008 Hugo van der Kooij ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; sub Round { my $input = $_[0]; my $digits = $_[1]; my $factor = (10**$digits); my $result = (int( ($input * $factor) + .5) / $factor); return $result; } #Init String Containers my ( $RECVtime, $SCANsize, $SCANtime, $delivery, $reason, $size, $time ); #Init Array my @RSaction = (); my @Saction = (); my @Reason = (); my @OtherList = (); #Init Hashes my ( %RECVReason, %RECVReasons, %RECVaction, %RECVreason, %RECVtime_div, %RECVtime_max, %RECVtime_min, %RECVtime_total, %SCANReason, %SCANReasons, %SCANaction, %SCANreason, %SCANsize_div, %SCANsize_total, %SCANtime_div, %SCANtime_max, %SCANtime_min, %SCANtime_total, %SENDaction, %SENDreason, %SENDresponse, %score_div, %score_max, %score_min, %score_total ); # Text messages for the RECV and SCAN action codes: $RSaction[0] = "Allowed Message(s)"; $RSaction[1] = "Aborted Message(s)"; $RSaction[2] = "Blocked Message(s)"; $RSaction[3] = "Quarantined Message(s)"; $RSaction[4] = "Tagged Message(s)"; $RSaction[5] = "Deferred Message(s)"; $RSaction[6] = "Per-User Quarantined Message(s)"; $RSaction[7] = "Whitelisted Message(s)"; # Text messages for the SEND action codes: $Saction[0] = "UNKNOWN"; $Saction[1] = "Delivered Message(s)"; $Saction[2] = "Rejected Message(s)"; $Saction[3] = "Deferred Message(s)"; $Saction[4] = "Expired Message(s)"; # Text messages for the reason codes: $Reason[0] = "Allowed"; $Reason[1] = "Virus detected"; $Reason[2] = "Banned attachment"; $Reason[3] = "RBL Match"; $Reason[4] = "Rate Control"; $Reason[5] = "Too Many Message(s) In Session"; $Reason[6] = "Timeout Exceeded"; $Reason[7] = "No such Domain"; $Reason[8] = "No such User"; $Reason[9] = "Subject Filter Match"; $Reason[10] = "UNKNOWN (10)"; $Reason[11] = "Client IP"; $Reason[12] = "Recipient Address Rejected"; $Reason[13] = "No valid Recipients"; $Reason[14] = "Domain Not Found"; $Reason[15] = "Sender Address Rejected"; $Reason[16] = "UNKNOWN (16)"; $Reason[17] = "Need Fully Qualified Recipient"; $Reason[18] = "Need Fully Qualified Sender"; $Reason[19] = "Unsupported Command"; $Reason[20] = "MAIL FROM Syntax Error"; $Reason[21] = "Bad Address Syntax"; $Reason[22] = "RCPT TO Syntax Error"; $Reason[23] = "Send EHLO/HELO First"; $Reason[24] = "Need MAIL Command"; $Reason[25] = "Nested MAIL Command"; $Reason[26] = "UNKNOWN (26)"; $Reason[27] = "EHLO/HELO Syntax Error"; $Reason[28] = "UNKNOWN (28)"; $Reason[29] = "UNKNOWN (29)"; $Reason[30] = "Mail Protocol Error"; $Reason[31] = "Score"; $Reason[32] = "UNKNOWN (32)"; $Reason[33] = "UNKNOWN (33)"; $Reason[34] = "Header Filter Match"; $Reason[35] = "Sender Block/Accept"; $Reason[36] = "Recipient Block/Accept"; $Reason[37] = "Body Filter Match"; $Reason[38] = "Message Size Bypass"; $Reason[39] = "Intention Analysis Match"; $Reason[40] = "SPF/Caller-ID"; $Reason[41] = "Client Host Rejected"; $Reason[42] = "UNKNOWN (42)"; $Reason[43] = "UNKNOWN (43)"; $Reason[44] = "Authentication Not Enabled"; $Reason[45] = "Allowed Message Size Exceeded"; $Reason[46] = "Too Many Recipients"; $Reason[47] = "Need RCPT Command"; $Reason[48] = "DATA Syntax Error"; $Reason[49] = "Internal Error"; $Reason[50] = "Too Many Hops"; $Reason[51] = "UNKNOWN (51)"; $Reason[52] = "UNKNOWN (52)"; $Reason[53] = "UNKNOWN (53)"; $Reason[54] = "UNKNOWN (54)"; $Reason[55] = "Invalid Parameter Syntax"; $Reason[56] = "STARTTLS Syntax Error"; $Reason[57] = "TLS Already Active"; $Reason[58] = "Too Many Errors"; $Reason[59] = "Need STARTTLS First"; $Reason[60] = "Spam Fingerprint Found"; while (defined(my $ThisLine = )) { if ( my ($address, $start_time, $end_time, $type, $info) = ($ThisLine =~ /([^\s]+)\s(?:[^\s]+)\s(\d+)\s(\d+)\s(RECV|SCAN|SEND)\s(.*)$/) ) { # ip variable not used # ($ip) = ($address =~ /\[(.*)\]/); $time = ($end_time - $start_time); if ( $type =~ /RECV/ ) { if ( my ($sender, $recipient, $action, $reason, $reason_extra) = ($info =~ /([^\s]+)\s([^\s]+)\s(\d+)\s(\d+)\s(.*)$/) ) { $RECVaction{$action}++; $RECVreason{$reason}++; if ( ($reason_extra =~ /^-[| ]$/) or ($reason < 1) ) { } else { $RECVReasons{$reason_extra}++; $RECVReason{$reason_extra} = $reason; } $RECVtime_total{$action} = ($RECVtime_total{$action} + $time); $RECVtime_div{$action}++; if ($time < $RECVtime_min{$action}) { $RECVtime_min{$action} = $time; } elsif ($RECVtime_min{$action} eq "") { $RECVtime_min{$action} = $time; } if ($time > $RECVtime_max{$action}) { $RECVtime_max{$action} = $time; } elsif ($RECVtime_max{$action} eq "") { $RECVtime_max{$action} = $time; } } else { push @OtherList,$ThisLine; } } elsif ( $type =~ /SCAN/ ) { if ( my ($enc, $sender, $recipient, $score, $action, $reason, $reason_extra) = ($info =~ /([^\s]+)\s([^\s]+)\s([^\s]+)\s([-\.\d+]+)\s(\d+)\s(\d+)\s(.*)\sSUBJ:(?:.*)$/) ) { if ( ($size) = ($reason_extra =~ /\sSZ:(\d+)$/) ) { $reason_extra =~ s/\sSZ:(\d+)$//; $SCANsize_total{$action} = ($SCANsize_total{$action} + $size); $SCANsize_div{$action}++; } $SCANaction{$action}++; $SCANreason{$reason}++; if ($score =~ /^-$/) { } else { $score_total{$action} = ($score_total{$action} + $score); $score_div{$action}++; if ($score < $score_min{$action}) { $score_min{$action} = $score; } elsif ($score_min{$action} eq "") { $score_min{$action} = $score; } if ($score > $score_max{$action}) { $score_max{$action} = $score; } elsif ($score_max{$action} eq "") { $score_max{$action} = $score; } } if ($reason_extra =~ /^-$/) { } else { $SCANReasons{$reason_extra}++; $SCANReason{$reason_extra} = $reason; } $SCANtime_total{$action} = ($SCANtime_total{$action} + $time); $SCANtime_div{$action}++; if ($time < $SCANtime_min{$action}) { $SCANtime_min{$action} = $time; } elsif ($SCANtime_min{$action} eq "") { $SCANtime_min{$action} = $time; } if ($time > $SCANtime_max{$action}) { $SCANtime_max{$action} = $time; } elsif ($SCANtime_max{$action} eq "") { $SCANtime_max{$action} = $time; } } else { push @OtherList,$ThisLine; } } elsif ( $type =~ /SEND/ ) { if ( my ($enc, $action, $response) = ($info =~ /([^\s]+)\s(\d+)\s(?:[^\s]+)\s(.*)$/) ) { $SENDaction{$action}++; if (($delivery) = ($response =~/^(\d+)\s.*/) ) { $SENDresponse{$delivery}++; } } else { push @OtherList,$ThisLine; } } else { push @OtherList,$ThisLine; } } elsif ( $ThisLine =~ /\s(RECV|SCAN|SEND)\s/) { push @OtherList,$ThisLine; } } ################################################################## if ($#OtherList >= 0) { print "\n\n**Unmatched Entries**\n\n"; print @OtherList; } if (keys %RECVaction) { print "\nReceive actions:\n"; foreach my $action (sort {$a cmp $b} keys %RECVaction) { print ' ' . $RSaction[$action] . ": ". $RECVaction{$action} . "\n"; } } if (keys %SCANaction) { print "\nScanning actions:\n"; foreach my $action (sort {$a cmp $b} keys %SCANaction) { print ' ' . $RSaction[$action] . ": ". $SCANaction{$action} . "\n"; } } if (keys %SENDaction) { print "\nSending actions:\n"; foreach my $action (sort {$a cmp $b} keys %SENDaction) { print ' ' . $Saction[$action] . ": ". $SENDaction{$action} . "\n"; } } if (keys %RECVreason) { print "\nBlocked incoming messages:\n"; foreach my $reason (sort {$a cmp $b} keys %RECVreason) { if ($reason > 0) { print ' ' . $Reason[$reason] . " : ". $RECVreason{$reason} . "\n"; } } } if ( ($Detail >= 10) and (keys %RECVReasons) ) { print "\nDetails of blocked incoming messages :\n"; foreach my $reason (sort {$a cmp $b} keys %RECVReasons) { print ' ' . $Reason[$RECVReason{$reason}] . " : " . $reason . " : ". $RECVReasons{$reason} . "\n"; } } if (keys %RECVtime_total) { print "\nReceive timing:\n"; foreach my $action (sort {$a cmp $b} keys %RECVtime_total) { $RECVtime = ($RECVtime_total{$action} / $RECVtime_div{$action}); print ' ' . $RSaction[$action] . " :\n"; print "\tMinimum : " . $RECVtime_min{$action} . "\n"; print "\tAverage : " . Round($RECVtime, 1) . "\n"; print "\tMaximum : " . $RECVtime_max{$action} . "\n"; } } if (keys %SCANreason) { print "\nDecisions while scanning:\n"; foreach my $reason (sort {$a cmp $b} keys %SCANreason) { print ' ' . $Reason[$reason] . " : ". $SCANreason{$reason} . "\n"; } } if ( ($Detail >= 10) and (keys %SCANReasons) ) { print "\nDetails of decisions while scanning:\n"; foreach $reason (sort {$a cmp $b} keys %SCANReasons) { print ' ' . $Reason[$SCANReason{$reason}] . " : " . $reason . " : ". $SCANReasons{$reason} . "\n"; } } if (keys %SCANtime_total) { print "\nScan timing:\n"; foreach my $action (sort {$a cmp $b} keys %SCANtime_total) { $SCANtime = ($SCANtime_total{$action} / $SCANtime_div{$action}); print ' ' . $RSaction[$action] . " :\n"; print "\tMinimum : " . $SCANtime_min{$action} . "\n"; print "\tAverage : " . Round($SCANtime, 1) . "\n"; print "\tMaximum : " . $SCANtime_max{$action} . "\n"; } } if (keys %SCANsize_total) { print "\nMessage sizes:\n"; foreach my $action (sort {$a cmp $b} keys %SCANsize_total) { $SCANsize = ($SCANsize_total{$action} / $SCANsize_div{$action}); print ' ' . $RSaction[$action] . " :\n"; # print "\tMinimum : " . $SCANsize_min{$action} . "\n"; print "\tAverage : " . Round($SCANsize, 0) . "\n"; # print "\tMaximum : " . $SCANsize_max{$action} . "\n"; } } if (keys %SENDreason) { print "\nSending reasons:\n"; foreach my $reason (sort {$a cmp $b} keys %SENDreason) { print ' ' . $Reason[$reason] . " : ". $SENDreason{$reason} . "\n"; } } if (keys %SENDresponse) { print "\nSending responses:\n"; foreach my $response (sort {$a cmp $b} keys %SENDresponse) { print ' ' . $response . ": ". $SENDresponse{$response} . "\n"; } } if (keys %score_total) { print "\nScores:\n"; foreach my $action (sort {$a cmp $b} keys %score_total) { my $score = ($score_total{$action} / $score_div{$action}); print ' ' . $RSaction[$action] . " :\n"; print "\tMinimum : " . $score_min{$action} . "\n"; print "\tAverage : " . Round($score, 3) . "\n"; print "\tMaximum : " . $score_max{$action} . "\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/systemd0000644000175000017500000004610115075123620020407 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2016 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $Ignore_failed = $ENV{'ignore_failed'} || ""; my $Ignore_leftover = $ENV{'ignore_leftover'} || ""; my $Ignore_messages = $ENV{'ignore_messages'} || '^$'; my %ConfigError; my %Activated; my %Failed; my %Deactivated; my %LeftOver; my %NotStopped; my $Reexecuted = 0; my %ReexecutedRequested; my %Reloaded; my %Skipped; my %Slice; my %Slow; my %Started; my %Target; my $TimeChanged = 0; my $LastTarget; my %UserSession; my %OtherList; #init String Containers my ( $Exe, $Service, $name, $pidfile, $reason, $service, $session, $target, $user, ); # Failure will generate multiple messages like: # EL7: # Feb 5 16:37:50 hostname systemd: ansible-pull.service: main process exited, code=exited, status=2/INVALIDARGUMENT # Feb 5 16:37:50 hostname systemd: Failed to start Run ansible-pull on boot. # Feb 5 16:37:50 hostname systemd: Unit ansible-pull.service entered failed state. # Feb 5 16:37:50 hostname systemd: ansible-pull.service failed. # EL8: # Feb 5 16:37:50 hostname systemd[1]: ansible-pull.service: Main process exited, code=exited, status=2/INVALIDARGUMENT # Feb 5 16:37:50 hostname systemd[1]: ansible-pull.service: Failed with result 'exit-code'. while (defined(my $ThisLine = )) { chomp($ThisLine); if ($ThisLine =~ /^(Activat|Deactivat|Expect|Mount|Unmount|Reload|Start|Stopp)ing / or $ThisLine =~ /^Finished / or # sssd users @ in usernames $ThisLine =~ /^Accepting user\/group name '.*\@.*', which does not match strict user\/group name rules\.$/ or # These events will be caught with the Failed with or failed message $ThisLine =~ /^Failed to start / or $ThisLine =~ /Failed to .* socket / or $ThisLine =~ /Failed at step / or $ThisLine =~ /([Cc]ontrol|[Mm]ain|[Mm]ount|[Ss]wap) process exited, code=(exited|killed|dumped),? status=/ or $ThisLine =~ /^Timed out / or $ThisLine =~ /^Unit.* entered failed state\.$/ or # Informational $ThisLine =~ /^(?:bpf-.*: )?LSM BPF program attached/ or $ThisLine =~ /^Closed .*\.$/ or $ThisLine =~ /: Consumed .+? CPU time/ or # crond will never restart process when it is restarted $ThisLine =~ /^crond\.service: Found left-over process \d+ \(.*\) in control group while starting unit\. Ignoring\.$/ or $ThisLine =~ /Current command vanished from the unit file, execution of the command list won't be resumed/ or $ThisLine =~ /^Received SIGINT\./ or $ThisLine =~ /^Detected (architecture|virtualization) / or # Spurious warning - should be fixed in later systemd (EL8.4) $ThisLine =~ /^Failed to connect to API bus: Connection refused/ or # Extransous scope messages with LanSweeper - revisit with EL8.4 $ThisLine =~ /: Failed to add PIDs to scope's control group: No such process/ or $ThisLine =~ /\.scope: Couldn't move process \d+ to requested cgroup '.*': No such process/ or $ThisLine =~ /scope: Failed with result 'resources'/ or $ThisLine =~ /session-[[:xdigit:]]+\.scope: Deactivated successfully\./ or $ThisLine =~ /^Found device / or $ThisLine =~ /Found dependency on / or $ThisLine =~ /Got automount request for \/proc\// or $ThisLine =~ /^Hostname set to / or $ThisLine =~ /^Inserted module / or $ThisLine =~ /^Listening on / or $ThisLine =~ /^Mounted / or $ThisLine =~ /^Queued start job for default target / or $ThisLine =~ /^Queuing reload/ or $ThisLine =~ /^Relabell?ed / or $ThisLine =~ /^Reloading\.+$/ or # Happens on each boot at switch root $ThisLine =~ /^RTC configured in / or $ThisLine =~ /^Running in init(?:ial RAM disk|rd)\.$/ or $ThisLine =~ /^selinux: avc: *received policyload notice/ or $ThisLine =~ /^selinux: avc: *op=load_policy / or $ThisLine =~ /^Set hostname to / or $ThisLine =~ /^(?:Set up|Unset) automount.*Arbitrary Executable File Formats File System Automount Point\.$/ or $ThisLine =~ /^Shutting down\.$/ or $ThisLine =~ /^Startup finished in / or $ThisLine =~ /^Stopped / or $ThisLine =~ /^Switching root\.$/ or $ThisLine =~ /: Succeeded\.$/ or $ThisLine =~ /^Successfully loaded SELinux policy in / or $ThisLine =~ /^Successfully made .* read-only/ or $ThisLine =~ /already active, refusing\./ or $ThisLine =~ /^Failed to propagate agent release message:/ or $ThisLine =~ /: Supervising process .* which is not our child\. We'll most likely not notice when it exits\.$/ or $ThisLine =~ /: Got notification message from PID \d+, but reception is disabled\./ or $ThisLine =~ /: Got notification message from PID \d+, but reception only permitted for main PID \d+/ or $ThisLine =~ /Cannot find unit for notify message of PID \d+/ or $ThisLine =~ /^systemd .* running in system mode/ or # This is preceeded by a more descriptive message $ThisLine =~ /^This usually indicates unclean termination of a previous run, or service implementation deficiencies\.$/ or $ThisLine =~ /Transaction (for .*)?is destructive/ or $ThisLine =~ /^Unit .* is bound to inactive unit .*\. Stopping, too\./ or $ThisLine =~ /Unit (.* is )?not needed anymore\. Stopping\./ or $ThisLine =~ /[Ss]tart(-pre)? operation timed out\. Terminating\./ or $ThisLine =~ /Job .* timed out\./ or $ThisLine =~ /[Ss]tate '.*' timed out\. Skipping SIGKILL\./ or $ThisLine =~ /[Ss]top.* timed out\. Killing\./ or $ThisLine =~ /hold-?off time over, scheduling restart\./ or $ThisLine =~ /has no hold-off time.*, scheduling restart\./ or $ThisLine =~ /Service Restart.* expired, scheduling restart\./ or $ThisLine =~ /Scheduled restart job, restart counter is at .*\./ or $ThisLine =~ /: Watchdog timeout/ or $ThisLine =~ /Watchdog running with a (?:hardware )?timeout of/ or $ThisLine =~ /^Dependency failed for / or # This is preceeded by a more descriptive message $ThisLine =~ / Triggering OnFailure= dependencies\./ or $ThisLine =~ /Processes still around after .*SIGKILL\./ or $ThisLine =~ /^Unmounted / or $ThisLine =~ /: Unit is bound to inactive unit / or $ThisLine =~ /^Unnecessary job was removed for / or $ThisLine =~ /[Hh]ardware watchdog / or # This is now trapped as a Slow Start error #$ThisLine =~ /PID file .* not readable \(yet\?\) after start/ or $ThisLine =~ /Failed to (read|parse) PID from file / or # Units can depend on files that do not exist $ThisLine =~ /Cannot add dependency job(:? for unit .*)?, ignoring: Unit (:?.* failed to load: No such file or directory|not found)\.$/ or # https://bugs.freedesktop.org/show_bug.cgi?id=90386 $ThisLine =~ /Dev(ice)? .* appeared twice with different sysfs paths .* and / or # Inactive units are sometimes reloaded $ThisLine =~ /^Unit .* cannot be reloaded because it is inactive\.$/ or $ThisLine =~ /^.*: Unit cannot be reloaded because it is inactive\.$/ or $ThisLine =~ / is not active\.$/ or $ThisLine =~ / is inactive\.$/ or # https://bugzilla.redhat.com/show_bug.cgi?id=1306452 $ThisLine =~ /^[^ ]*\.mount: Directory \/[^ ]* to mount over is not empty, mounting anyway\.$/ or # A known issue - reported by multiple distributions $ThisLine =~ /^user\@\d+\.service: Failed at step CGROUP spawning \/usr\/lib\/systemd\/systemd: No such file or directory$/ or $ThisLine =~ /^Received SIGRTMIN\+2[01] from PID \d+ \((?:plymouthd|n\/a)\)\.$/ or # https://bugzilla.redhat.com/show_bug.cgi?id=1072368 $ThisLine =~ /^Received SIGRTMIN\+24 from PID \d+ \((?:kill|n\/a)\)\.$/ or $ThisLine =~ /: Killing process \d+ \(.*\) with signal SIG.+\.$/ or $ThisLine =~ /: Sent signal SIGHUP to main process .* on client request\.$/ or $ThisLine =~ /^Removed slice / or $ThisLine =~ /^pam_unix\(systemd-user:session\): session (?:opened|closed) for user/ or $ThisLine =~ /Adding .* random time\.$/ or # https://bugzilla.redhat.com/show_bug.cgi?id=1890632 $ThisLine =~ /Not generating service for XDG autostart .*,/ or $ThisLine =~ /gnome-systemd-autostart-condition not found/ or $ThisLine =~ /kde-systemd-start-condition not found/ or $ThisLine =~ /Unknown key name .* in section 'Desktop Entry'/ or # These happen on every shutdown - downgraded to debug message in systemd v235 # https://github.com/systemd/systemd/issues/6777 $ThisLine =~ /^Failed to propagate agent release message: (?:Connection reset by peer|Transport endpoint is not connected)/ or $ThisLine =~ /^cgroup compatibility translation between legacy and unified hierarchy settings activated\. See cgroup-compat debug messages for details\.$/ or $ThisLine =~ /^\(This warning is only shown for the first unit using IP firewalling\.\)$/ or $ThisLine =~ /^.*\.socket: Socket service .* already active/ or # User specified ignore messages, lower cased $ThisLine =~ /$Ignore_messages/i ) { # Ignore these } elsif (($service,$reason) = ($ThisLine =~ /^Configuration file ([^ ]*) is ([^.]*)\./)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^\[(.*)\] (Support for option .* has been removed) and it is ignored/)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^(.*):\d+: (.* is obsolete, .*)\. /)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^(.*):\d+: (.* this is not safe!)/)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^(.*):\d+: (PIDFile= references a path below legacy directory .*), /)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^\[?([^\]:]+(?::\d+)?)[\]:]? (Unknown .* in section '.*')/)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^(.*): (unit configures an IP firewall, but not running as root)\.$/)) { $ConfigError{$reason}{$service}++; } elsif (($service,$reason) = ($ThisLine =~ /^(bpf-lsm): (.*)$/)) { $ConfigError{$reason}{$service}++; } elsif (($reason,$service) = ($ThisLine =~ /^((BPF LSM) .*, LSM BPF not supported)$/)) { $ConfigError{$reason}{$service}++; } elsif (($reason,$service) = ($ThisLine =~ /^(ConfigurationDirectory .*)\. \((.*)\)$/)) { $ConfigError{$reason}{$service}++; } elsif (($reason,$service) = ($ThisLine =~ /^(System is tainted): (.*)$/)) { $ConfigError{$reason}{$service}++; # Skipped goes before failed to catch "was skipped because all trigger condition checks failed." } elsif (($name) = ($ThisLine =~ /^Condition check resulted in (.*) being skipped\.$/)) { $Skipped{$name}++ if $Detail; } elsif (($name) = ($ThisLine =~ /^(.*): Skipped due to /)) { $Skipped{$name}++ if $Detail; } elsif (($name) = ($ThisLine =~ /^(.*) was skipped because/)) { $Skipped{$name}++ if $Detail; } elsif (($service) = ($ThisLine =~ /(\S+): Failed (:?to execute command|with result)/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /(\S+) failed\.$/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /^(.*) failed with ((error code)|(exit status)) \d+\.$/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /^(.*): Start request repeated too quickly\.$/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /^(.*): Job .* failed with result/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /^Job (.*) failed with result/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /^Failed (unmounting .*)\.$/)) { $Failed{$service}++; } elsif (($service) = ($ThisLine =~ /^Failed to (listen on .*)\.$/)) { $Failed{$service}++; } elsif (($target) = ($ThisLine =~ /^Reached target (.*)\.$/)) { $Target{$target}++; $LastTarget = $target; } elsif (($session, $user) = ($ThisLine =~ /^Started (?:session-[[:xdigit:]]+\.scope - )?Session ([[:xdigit:]]+) of [uU]ser (.*)\.$/)) { $UserSession{$user}->{$session}++; } elsif (($service) = ($ThisLine =~ /^Activated (.*)\.$/)) { $Activated{$service}++; } elsif (($service) = ($ThisLine =~ /^Started (.*)\.$/)) { $Started{$service}++; } elsif (($service) = ($ThisLine =~ /^Reloaded (.*)\.$/)) { $Reloaded{$service}++; } elsif (($service) = ($ThisLine =~ /^Deactivated (.*)\.$/)) { $Deactivated{$service}++; } elsif (($service) = ($ThisLine =~ /(.*): Deactivated /)) { $Deactivated{$service}++; } elsif ($ThisLine eq "Reexecuting.") { $Reexecuted++ if $Detail; } elsif ($ThisLine =~ "Reexecuting requested from client (.*)") { $ReexecutedRequested{$1}++ if $Detail; } elsif ($ThisLine =~ /^Time has been changed$/) { $TimeChanged++; } elsif (my ($slice) = ($ThisLine =~ /^Created slice (.*)\.$/)) { $Slice{$slice}++; } elsif (($pidfile) = ($ThisLine =~ /^PID file (.*) not readable \(yet\?\) after start\.$/)) { $Slow{$pidfile}++; } elsif (($pidfile) = ($ThisLine =~ /Can't open PID file (.*) \(yet\?\) after start:/)) { $Slow{$pidfile}++; } elsif (($Service, $Exe) = ($ThisLine =~ /^(.*): Found left-over process \d+ \((.*)\) in control group while starting unit\. Ignoring\.$/)) { $LeftOver{"$Service:$Exe"}++ unless "$Service:$Exe" =~/^$Ignore_leftover$/i; } elsif (($Service, $Exe) = ($ThisLine =~ /^(.*): Unit process \d+ \((.*)\) remains running after unit stopped\.$/)) { $NotStopped{"$Service:$Exe"}++ unless "$Service:$Exe" =~/^$Ignore_leftover$/i; } else { $OtherList{$ThisLine}++; } } if (keys %ConfigError) { print "Configuration errors:\n"; foreach my $reason (sort {$a cmp $b} keys %ConfigError) { my $tot = 0; print " $reason"; foreach my $service (sort {$a cmp $b} keys %{$ConfigError{$reason}}) { $tot += $ConfigError{$reason}{$service}; if ($Detail >= 10) { print "\n $service: $ConfigError{$reason}{$service} Time(s)"; } } if ($Detail < 10) { print ": $tot Time(s)" } print "\n"; } print "\n"; } # Because we set Failed in multiple locations, cleanup once here foreach my $item (keys %Failed) { delete $Failed{$item} if ($item =~ /^$Ignore_failed$/i); } if (keys %Failed) { print "ERROR: Failed state:\n"; foreach my $item (sort {$a cmp $b} keys %Failed) { print " $item: $Failed{$item} Time(s)\n"; } print "\n"; } # Detail >= 1 if ($Reexecuted) { if (keys %ReexecutedRequested) { print "Reexecuted sytemd requested by:\n"; foreach my $item (sort {$a cmp $b} keys %ReexecutedRequested) { print " $item: $ReexecutedRequested{$item} Time(s)\n"; } print "\n"; } else { print "Reexecuted systemd: $Reexecuted Time(s)\n\n"; } } if (keys %Skipped) { print "Condition check resulted in the following being skipped:\n"; foreach my $item (sort {$a cmp $b} keys %Skipped) { print " $item: $Skipped{$item} Time(s)\n"; } print "\n"; } if (keys %LeftOver) { print "Warning: Found left-over process in control group while starting unit:\n"; foreach my $item (sort {$a cmp $b} keys %LeftOver) { my ($service, $exe) = split(":", $item); print " $service($exe): $LeftOver{$item} Time(s)\n"; } print "\n"; } if (keys %NotStopped) { print "Warning: Remained running after unit stopped:\n"; foreach my $item (sort {$a cmp $b} keys %NotStopped) { my ($service, $exe) = split(":", $item); print " $service($exe): $NotStopped{$item} Time(s)\n"; } print "\n"; } if (keys %Target && $Detail > 3) { print "Reached target $LastTarget: $Target{$LastTarget} Time(s)"; if ($Detail > 10) { print ", and:\n"; foreach my $target (sort {$a cmp $b} keys %Target) { print " $target: $Target{$target} Time(s)\n"; } } else { print "\n"; } print "\n"; } if (keys %Started && $Detail > 3) { print "Started:\n"; foreach my $started (sort {$a cmp $b} keys %Started) { print " $started: $Started{$started} Time(s)\n"; } print "\n"; } if (keys %Activated && $Detail > 3) { print "Activated:\n"; foreach my $item (sort {$a cmp $b} keys %Activated) { print " $item: $Activated{$item} Time(s)\n"; } print "\n"; } if (keys %Slow && $Detail > 3) { print "Slow to start:\n"; foreach my $pidfile (sort {$a cmp $b} keys %Slow) { print " $pidfile: $Slow{$pidfile} Time(s)\n"; } print "\n"; } if (keys %Reloaded && $Detail > 5) { print "Reloaded:\n"; foreach my $item (sort {$a cmp $b} keys %Reloaded) { print " $item: $Reloaded{$item} Time(s)\n"; } print "\n"; } if (keys %Deactivated && $Detail > 5) { print "Deactivated:\n"; foreach my $item (sort {$a cmp $b} keys %Deactivated) { print " $item: $Deactivated{$item} Time(s)\n"; } print "\n"; } if ($TimeChanged && $Detail > 5) { print "Time Changed: $TimeChanged Time(s)\n\n"; } if (keys %UserSession && $Detail > 3) { print "User Sessions:\n"; foreach my $user (sort {$a cmp $b} keys %UserSession) { print " $user: "; if ($Detail >= 10) { foreach my $session (sort {$a cmp $b} keys %{$UserSession{$user}}) { print " $session"; } print "\n"; } else { print scalar(keys %{$UserSession{$user}}) . ": Time(s)"; } print "\n"; } } if (keys %Slice && $Detail > 5) { print "Slices created:\n"; foreach my $slice (sort {$a cmp $b} keys %Slice) { print " $slice: $Slice{$slice} Time(s)\n"; } print "\n"; } if (keys %OtherList) { print "\n\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/omsa0000644000175000017500000001477015075123620017665 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## ## Copyright (c) 2014 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; use warnings; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $IgnoreNonCertifiedDrives = $ENV{'omsa_ignore_non_certified_drives'} || 0; my %ServiceError; my %ServiceMessage; my %OtherList; # Lines are of the form: # 3017 2048 - Storage Service Device failed: Battery 0 Controller 0 # \d+ \d+ - (\w+) Service (.*) while (defined(my $ThisLine = )) { chomp($ThisLine); my ($Service, $Severity, $Category, $MessageID, $Message); if (($Service, $Severity, $Category, $MessageID, $Message) = ($ThisLine =~ /^\d+ \d+ - (\w+) Service Severity: ([^,]+), Category: ([^,]+), MessageID: ([^,]+), Message: (.*)/)) { } else { # Old style ($Service,$Message) = ($ThisLine =~ /^\d+ \d+ - (\w+) Service (.*)$/); } if ($Message =~ /error|fail|degraded|disable|replace|timeout/i or (defined($Severity) and $Severity eq "Critical")) { # Service erroneously detects failure on service startup next if (($Service eq "Instrumentation") and $Message =~ /^Power supply detected a failure.*Previous state was: Unknown/); $ServiceError{$Service}->{$Message}++; } elsif (defined($Service)) { # Skip informational messages if needed if ($Service eq "Instrumentation") { # Service erroneously detects absence on service startup next if ($Message =~ /^Battery sensor detected absence value/); next if (($Message =~ /^Detected new peak power value/) and ($Detail < 5)); next if (($Message =~ /IPMI status.*[Ii]nterface:/) and ($Detail < 10)); next if (($Message =~ /Administrator .* (?:start|stopped).*/) and ($Detail < 10)); next if (($Message =~ /^Systems Management Data Manager (?:Started|Stopped)/) and ($Detail < 10)); } elsif ($Service =~ "Storage") { next if (($Message =~ /^Controller.* event log: Battery (?:Present|charge complete|started charging|temperature is normal)/) and ($Detail < 5)); next if (($Message =~ /^Controller.* event log: Controller operating temperature within normal range/) and ($Detail < 5)); next if (($Message =~ /^Controller.* event log: (Board Revision|Controller hardware revision ID)/) and ($Detail < 10)); next if (($Message =~ /^Controller.* event log: Current capacity of the battery is above threshold/) and ($Detail < 5)); next if (($Message =~ /^Controller.* event log: Enclosure .* (:?communication restored|discovered)/) and ($Detail < 10)); next if (($Message =~ /^Controller.* event log: Firmware initialization started/) and ($Detail < 10)); next if (($Message =~ /^Controller.* event log: Host driver is loaded and operational/) and ($Detail < 5)); next if (($Message =~ /^Controller.* event log: Inserted:/) and ($Detail < 5)); next if (($Message =~ /^Controller.* event log: PD .* is not a certified drive/) and ($IgnoreNonCertifiedDrives)); next if (($Message =~ /^Controller.* event log: Package version/) and ($Detail < 10)); next if (($Message =~ /^Controller.* event log: Shutdown command received from host/) and ($Detail < 1)); next if (($Message =~ /^Controller.* event log: Time established as/) and ($Detail < 10)); next if (($Message =~ /^Controller.* event log: Unexpected sense: Encl PD .* CDB: 12 00 00 00 (:?04|20) 00, Sense: 5\/24\/00/) and ($IgnoreNonCertifiedDrives)); next if (($Message =~ /^Controller.* event log: Unexpected sense: PD .* CDB: 12 01 dc 01 1d 00, Sense: (4\/cf|5\/24)\/00/) and ($IgnoreNonCertifiedDrives)); next if (($Message =~ /Disk .* is not supplied by an authorized hardware provider/) and ($IgnoreNonCertifiedDrives)); next if (($Message =~ /Patrol Read .*(started|stopped|resumed|completed)/) and ($Detail < 5)); next if (($Message =~ /SCSI sense data:? \(?Sense key: 5 Sense code: 24 Sense qualifier: 0/) and ($IgnoreNonCertifiedDrives)); next if (($Message =~ /Unexpected sense: PD .*, Sense: b\/00\/00/) and ($IgnoreNonCertifiedDrives)); next if (($Message =~ /^The battery charge cycle is complete\./) and ($Detail < 5)); next if (($Message =~ /^The controller battery Learn cycle will start in (?:\d+) days\./) and ($Detail < 5)); next if (($Message =~ /^The Patrol Read has (started|stopped|resumed)/) and ($Detail < 5)); } $ServiceMessage{$Service}->{$Message}++; } else { $OtherList{$ThisLine}++; } } if (keys %ServiceError) { print "ERRORS:\n"; foreach my $Service (sort {$a cmp $b} keys %ServiceError) { print " $Service Service:\n"; foreach my $Error (sort {$a cmp $b} keys %{$ServiceError{$Service}}) { print " $Error $ServiceError{$Service}->{$Error} Time(s)\n"; } print "\n"; } } if (keys %ServiceMessage) { print "Informational:\n"; foreach my $Service (sort {$a cmp $b} keys %ServiceMessage) { print " $Service Service:\n"; foreach my $Message (sort {$a cmp $b} keys %{$ServiceMessage{$Service}}) { print " $Message $ServiceMessage{$Service}->{$Message} Time(s)\n"; } print "\n"; } } if (keys %OtherList) { print "\n\n**Unmatched Entries**\n"; foreach my $line (sort {$a cmp $b} keys %OtherList) { print " $line: $OtherList{$line} Time(s)\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/windows0000644000175000017500000004106715075123620020417 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ########################################################################## # This is a logwatch script that looks at a log file composed of windows auth # security logs counts the number of times a user failed to login and # optionally the number times they successfully logged in and some other account # creation/modification audits. # # See the following sites for event id documentation: # http://www.microsoft.com/technet/prodtechnol/windowsserver2003/technologies/security/bpactlck.mspx # http://support.microsoft.com/?id=299475 # http://support.microsoft.com/?id=301677 ####################################################### ## Copyright (c) 2008 William Roumier ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':all'; use strict; my $detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; #Init Hashes my ( %accountChanged, %accountDeleted, %accountEnabled, %accountTypeChange, %auditLogCleared, %auditPolChange, %changePasswordAttempt, %domainPolicyChanged, %firewall, %groupMemberAdded, %groupMemberRemoved, %ikeSecNegFail, %krbAuthFailByAccount, %krbAuthFailByHost, %lockedOut, %loginFailByAccount, %loginFailByHost, %loginSuccess, %newAccount, %newDomainTrust, %passwordSet, %renewed, %rightsAdded, %rightsRemoved, %rmDomainTrust, %second_total, %sgtGranted, %tgtGranted, %third_total, %unmatchedFail, ); my ($month, $day, $time, $host, $process, $eventid, $msg); # Loop through the given input and parse it first to make sure we need to, # then to sort it into various categories. while (defined(my $line = )) { ($month, $day, $time, $host, $process, $eventid, $msg) = split(/\s+/, $line, 7); chomp($host); chomp($process); chomp($eventid); chomp($msg); if ($process =~ /security\[failure\]/) { # failure events # First count the number of failed logins - we always want that. # Events 529 - 537, 539 if (($eventid >= 529 && $eventid <= 537) || $eventid == 539 || $eventid == 680) { $msg =~ /(User Name|Logon account):\s*(\S+)\s+.+(Address|Workstation( Name)?):\s*\\{0,2}(\S+)/; # print "DEBUG Logon Failure: name:$2 source:$4\n"; $loginFailByAccount{$2}{$5}++; $loginFailByHost{$5}{$2}++; } # IKE Failures: Events 544 - 547 #elsif ($eventid >= 544 && $eventid <= 547 && $line =~ /some_meaningful_pattern/) { #} elsif ($eventid == 547) { # IKE security association negotiation failed $msg =~ /IKE Peer Addr\s+(\S+)\s+.+Failure Reason:\s*(.+)\s+Extra Status:/; #print "DEBUG IKE security association negotiation failed: host:$host peer:$1 reason:$2\n"; $ikeSecNegFail{$host}{$1}{$2}++; } elsif ($eventid == 644) { # User Account Locked Out $msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; #print "DEBUG User Account Locked Out: $1 on $2 by $3\n"; $lockedOut{$host}{$1}{$2}++; } elsif ($eventid == 675 || $eventid == 676 || $eventid == 677) { # Kerberos authentication failed $msg =~ /User Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/; #print "DEBUG Pre-Authentication failed: $1 on $2\n"; #$krbAuthFail{$host}{$1}{$2}++; $krbAuthFailByAccount{$1}{$2}++; $krbAuthFailByHost{$2}{$1}++; } #elsif ($eventid == 681) { # Logon Failure - not used in Windows 2003/XP # $msg =~ /The logon to account:\s*(\S+)\s+by:\s*(\S+)\s+from workstation:\s*(\S+)\s+failed/; # print "DEBUG Logon Failure to $1 by $2 from $3\n"; # $logonFailure{$1}{$2}{$3}++; #} elsif ($eventid == 861) { # The Windows Firewall has detected an application listenin for incoming traffic. $msg =~ /Path:\s*(\S+)\s+.+User account:\s*(\S+)\s+.+IP protocol:(\S+)\s+Port number:\s*(\S+)\s+Allowed:\s*(\S+)/; #print "DEBUG Server Application Firewalled: path:$1 account:$2 port:$3 $4 allowed:$5\n"; $firewall{$host}{$2}{$1}{$3}{$4}{$5}++; } else { # unmatched catch all chomp($msg); $unmatchedFail{$eventid . " " . $msg}++; } } elsif ($detail > 3 && $process =~ /security\[success\]/) { # success events if ($eventid == 528 || $eventid == 540 || $eventid == 680) { # Successful Logon $msg =~ /(User Name|Logon account|Account Name):\s*(\S+)\s+.+(Address|Workstation( Name)?):\s*\\{0,2}(\S+)/; if ($2 !~ /^-|\S+\$$/) { # ignore machines and anonymous #print "DEBUG Logon Success name:$2 source:$5\n"; $loginSuccess{$2}{$5}++; } } elsif ($eventid == 517) { # Audit log was cleared. $msg =~ /Primary User Name:\s*(\S+)\s+.*Client User Name:\s*(\S+)/; # print "DEBUG Audit log cleared: host:$host primary:$1 client:$2\n"; $auditLogCleared{$host}{$1}{$2}++; } elsif ($eventid == 608) { # User Right Assigned $msg =~ /User Right:\s*(\S+)\s+Assigned To:\s*(\S+)\s+Assigned By:\s+User Name:\s*(\S+)/; # print "DEBUG Rights Added: right:$1 to:$2 by:$3\n"; $rightsAdded{$host}{$3}{$2}{$1}++; } elsif ($eventid == 609) { # User Right Removed $msg =~ /User Right:\s*(\S+)\s+Removed From:\s*(\S+)\s+Removed By:\s+User Name:\s*(\S+)/; # print "DEBUG Rights Removed: right:$1 from:$2 by:$3\n"; $rightsRemoved{$host}{$3}{$2}{$1}++; } elsif ($eventid == 610) { # New Trusted Domain $msg =~ /New Trusted Domain\s+Domain( Name)?:\s*(\S+)\s+.*Established By:\s+User Name:\s*(\S+)/; # print "DEBUG New Trusted Domain: domain:$2 user:$3 host:$host\n"; $newDomainTrust{$host}{$2}{$3}++; } elsif ($eventid == 611) { # Removing Trusted Domain $msg =~ /Removing Trusted Domain\s+Domain( Name)?:\s*(\S+)\s+.*Established By:\s+User Name:\s*(\S+)/; # print "DEBUG New Trusted Domain: domain:$2 user:$3 host:$host\n"; $rmDomainTrust{$host}{$2}{$3}++; } elsif ($eventid == 612) { # Audit Policy Changed $msg =~ /Changed By:\s+User Name:\s*(\S+)/; # print "DEBUG Audit Policy Changed by $1 on $host\n"; $auditPolChange{$host}{$1}++; } # Group all account types together - should be clear what's what. elsif ($eventid == 624 || $eventid == 631 || $eventid == 635 || $eventid == 645 || $eventid == 653 || $eventid == 658 || $eventid == 663) { # Account Created $msg =~ /New Account Name:\s*(\S+)\s+.*Caller User Name:\s*(\S+)/; #print "DEBUG Account Created: $1 by $2 on $host\n"; $newAccount{$host}{$2}{$1}++; } elsif ($eventid == 625) { # User Account Type Change $msg =~ /Target Account Name:\s*(\S+)\s+.+New Type:\s*(\S+)\s*Caller User Name:\s*(\S+)/; # print "DEBUG User Account Type Change: $1 to $2 by $3 on $host\n"; $accountTypeChange{$host}{$3}{$1}{$2}++; } elsif ($eventid == 626) { # User Account Enabled $msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; # print "DEBUG User Account Enabled: $1 by $2 on $host\n"; $accountEnabled{$host}{$2}{$1}++; } elsif ($eventid == 627) { # Change Password Attempt $msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; # print "DEBUG Change Password Attempt: $1 by $2 on $host\n"; $changePasswordAttempt{$host}{$2}{$1}++; } elsif ($eventid == 628) { # User Account password set $msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; # print "DEBUG User Account password set: $1 by $2 on $host\n"; $passwordSet{$host}{$2}{$1}++; } elsif ($eventid == 630 || $eventid == 634 || $eventid == 638 || $eventid == 647 || $eventid == 652 || $eventid == 657 || $eventid == 662 || $eventid == 667) { # User Account Deleted $msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; # print "DEBUG Account Deleted: $1 by $2 on $host\n"; $accountDeleted{$host}{$2}{$1}++; } # Note: This doesn't distinguish between Global and Local Groups elsif ($eventid == 632 || $eventid == 636 || $eventid == 650 || $eventid == 655 || $eventid == 660 || $eventid == 665) { # Group Member Added $msg =~ /Member Name:\s*(\S+)\s+.+Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; # print "DEBUG Group Member Added: $1 to $2 by $3 on $host\n"; $groupMemberAdded{$host}{$2}{$3}{$1}++; } elsif ($eventid == 633 || $eventid == 637 || $eventid == 651 || $eventid == 656 || $eventid == 661 || $eventid == 666) { # Group Member Removed $msg =~ /Member Name:\s*(\S+)\s+.+Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; #print "DEBUG Group Member Removed: $1 to $2 by $3 on $host\n"; $groupMemberRemoved{$host}{$2}{$3}{$1}++; } elsif ($eventid == 639 || $eventid == 641 || $eventid == 642 || $eventid == 646 || $eventid == 649 || $eventid == 654 || $eventid == 659 || $eventid == 664 || $eventid == 668) { # Account Changed $msg =~ /Target Account Name:\s*(\S+)\s+.+Caller User Name:\s*(\S+)/; #print "DEBUG Account Changed: $1 by $2 on $host\n"; $accountChanged{$host}{$2}{$1}++; } elsif ($eventid == 643) { # Domain Policy Changed $msg =~ /Domain Policy Changed:\s*(.+) modified.+Caller User Name:\s*(\S+)/; #print "DEBUG Domain Policy Changed: $2 on $host to $1\n"; $domainPolicyChanged{$host}{$2}{$1}++; } elsif ($detail > 5 && $eventid == 672) { # Authentication Ticket Granted $msg =~ /User Name:\s*(\S+)\s+.+Service Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/; #print "DEBUG TGT Granted to $1 for $2 from $3\n"; $tgtGranted{$host}{$1}{$2}{$3}++; } elsif ($detail > 5 && $eventid == 673) { # Service Ticket Granted $msg =~ /User Name:\s*(\S+)\s+.+Service Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/; #print "DEBUG SGT Granted to $1 for $2 from $3\n"; $sgtGranted{$host}{$1}{$2}{$3}++; } elsif ($detail > 5 && $eventid == 674) { # Ticket Granted Renewed $msg =~ /User Name:\s*(\S+)\s+.+Service Name:\s*(\S+)\s+.+Client Address:\s*(\S+)/; #print "DEBUG Ticket Renewal granted to $1 for $2 from $3\n"; $renewed{$host}{$1}{$2}{$3}++; } } } # Always print login failures grouped by name and host in that order. if (keys %loginFailByAccount) { printLevel2("Windows Failed Logins by Account, Host", \%loginFailByAccount); } if (keys %loginFailByHost) { printLevel2("Windows Failed Logins by Host, Account", \%loginFailByHost); } if (keys %krbAuthFailByAccount) { printLevel2("Kerberos Authentication Failures by Account, Host", \%krbAuthFailByAccount); } if (keys %krbAuthFailByHost) { printLevel2("Kerberos Authentication Failures by Host, Account", \%krbAuthFailByHost); } if (keys %lockedOut) { printLevel3("Account Locked Out by Host, Target, Caller", \%lockedOut); } if (keys %ikeSecNegFail) { printLevel3("IKE Security Association Negotiation Failed by Host, Peer, Reason", \%ikeSecNegFail); } if (keys %unmatchedFail) { print "\t---- Unmatched Failure Audits ----\n\n"; foreach $msg (keys %unmatchedFail) { print "\t" . $unmatchedFail{$msg} . " Time(s): $msg\n"; } } # Start printing some other optional data like login successes, account creation/modification audits, etc. if ($detail > 3) { if (keys %loginSuccess) { printLevel2("Windows Successful Logins by Account, Host", \%loginSuccess); } if ($detail > 5) { if (keys %tgtGranted) { printLevel4("TGT Granted by Host, Account, Service, Client Addr", \%tgtGranted); } if (keys %sgtGranted) { printLevel4("SGT Granted by Host, Account, Service, Client Addr", \%sgtGranted); } if (keys %renewed) { printLevel4("Ticket Renewed by Host, Account, Service, Client Addr", \%renewed); } } if (keys %auditLogCleared) { printLevel3("Audit Log Cleared by Host, Primary Account, Client Account", \%auditLogCleared); } if (keys %rightsAdded) { printLevel4("User Rights Added by Host, Modifier, Account, Right", \%rightsAdded); } if (keys %rightsRemoved) { printLevel4("User Rights Removed by Host, Modifier, Account, Right", \%rightsRemoved); } if (keys %newDomainTrust) { printLevel3("New Domain Trust by Host, Domain, Modifier", \%newDomainTrust); } if (keys %rmDomainTrust) { printLevel3("Domain Trust Removed by Host, Domain, Modifier", \%rmDomainTrust); } if (keys %auditPolChange) { printLevel2("Audit Policy Changed by Host, Modifier", \%auditPolChange); } if (keys %newAccount) { printLevel3("New Accounts by Host, Modifier, Account", \%newAccount); } if (keys %accountTypeChange) { printLevel4("Account Type Changed by Host, Modifier, Account, Type", \%accountTypeChange); } if (keys %accountEnabled) { printLevel3("Account Enabled by Host, Modifier, Account", \%accountEnabled); } if (keys %changePasswordAttempt) { printLevel3("Change Password Attempt by Host, Modifier, Account", \%changePasswordAttempt); } if (keys %passwordSet) { printLevel3("Password Set by Host, Modifier, Account", \%passwordSet); } if (keys %accountDeleted) { printLevel3("Account Deleted by Host, Modifier, Account", \%accountDeleted); } if (keys %groupMemberAdded) { printLevel4("Group Member Added by Host, Group, Modifier, Account", \%groupMemberAdded); } if (keys %groupMemberRemoved) { printLevel4("Group Member Removed by Host, Group, Modifier, Account", \%groupMemberRemoved); } if (keys %accountChanged) { printLevel3("Account Changed by Host, Modifier, Account", \%accountChanged); } if (keys %domainPolicyChanged) { printLevel3("Domain Policy Changed by Host, Modifier, Change", \%domainPolicyChanged); } } # Prints a hash that's two levels deep in a generic hierarchical manor sub printLevel2 { my $msg = $_[0]; my %data = %{$_[1]}; print "\n\t---- $msg ----\n\n"; foreach my $first (sort(keys %data)) { my $total = 0; foreach my $second (keys %{$data{$first}}) { $total += $data{$first}{$second}; } print "\t" . LookupIP($first) . " $total Time(s)\n"; foreach my $second (sort(keys %{$data{$first}})) { print "\t\t" . LookupIP($second) . " " . $data{$first}{$second} . " Time(s)\n"; } print "\n"; } } # Prints a hash that's three levels deep in a generic hierarchical manor sub printLevel3 { my $msg = $_[0]; my %data = %{$_[1]}; print "\n\t---- $msg ----\n\n"; foreach my $first (sort(keys %data)) { my $first_total = 0; foreach my $second (keys %{$data{$first}}) { $second_total{$second} = 0; foreach my $third (keys %{$data{$first}{$second}}) { $second_total{$second} += $data{$first}{$second}{$third}; } $first_total += $second_total{$second}; } print "\t" . LookupIP($first) . ": $first_total Time(s)\n"; foreach my $second (sort(keys %{$data{$first}})) { print "\t\t" . LookupIP($second) . ": $second_total{$second} Time(s)\n"; foreach my $third (sort(keys %{$data{$first}{$second}})) { print "\t\t\t" . LookupIP($third) . " $data{$first}{$second}{$third} Time(s)\n"; } } print "\n"; } } # Prints a hash that's four levels deep in a generic hierarchical manor sub printLevel4 { my $msg = $_[0]; my %data = %{$_[1]}; print "\n\t---- $msg ----\n\n"; foreach my $first (sort(keys %data)) { my $first_total = 0; foreach my $second (keys %{$data{$first}}) { $second_total{$second} = 0; foreach my $third (keys %{$data{$first}{$second}}) { $third_total{$second}{$third} = 0; foreach my $fourth (keys %{$data{$first}{$second}{$third}}) { $third_total{$second}{$third} += $data{$first}{$second}{$third}{$fourth}; } $second_total{$second} += $third_total{$second}{$third}; } $first_total += $second_total{$second}; } print "\t" . LookupIP($first) . ": $first_total Time(s)\n"; foreach my $second (sort(keys %{$data{$first}})) { print "\t\t" . LookupIP($second) . ": $second_total{$second} Time(s)\n"; foreach my $third (sort(keys %{$data{$first}{$second}})) { print "\t\t\t" . LookupIP($third) . ": $third_total{$second}{$third} Time(s)\n"; foreach my $fourth (sort(keys %{$data{$first}{$second}{$third}})) { print "\t\t\t\t" . LookupIP($fourth) . " $data{$first}{$second}{$third}{$fourth} Time(s)\n"; } } } print "\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/cisco0000644000175000017500000013571715075123620020033 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Laurent DUFOUR , # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Laurent DUFOUR , # Heavily modified by: # Hugo van der Kooij # based on the work of # Kirk Bauer # # Please send all comments, suggestions, bug reports, # etc, to laurent.dufour@havas.com and hvdkooij@vanderkooij.org ######################################################## ######################################################## ## Copyright (c) 2008 Laurent DUFOUR ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use Logwatch ':all'; use strict; my $Debug = ValueOrDefault($ENV{'LOGWATCH_DEBUG'}, 0); my $Detail = ValueOrDefault($ENV{'LOGWATCH_DETAIL_LEVEL'}, 0); my $CatchUnknown = ValueOrDefault($ENV{'catch_unknown'}, 1); my $DebugCounter = 0; #Init String Containers my ( $ACL, $IPV6_packets, $InspectDrop, $JavaBlock, $SSH_packets, $Unit, $accesslist, $action, $address, $count, $destination, $destination_ip, $destination_port, $drophttppkts, $dropsmtppkts, $errortype, $file, $hash, $icmp_type, $interface, $interface_experiencing_error, $line, $lines, $message, $ntppear, $packets, $protocol, $radio, $source, $source_ip, $source_port, $state, $testline, $unmatched, $username, $vlan_number, $vty, $withwho, ); #Init Arrays my (@dfields, @sfields, @testfields) = (); #Init Hashes my ( %AAABadReg, %AAAServerUndef, %ACL, %ACLmissed, %ACTION, %ConfigChange, %Configured, %CountersMsg, %DHCPConflict, %DHCPPingConflict, %Dot11Assoc, %Dot11Disassoc, %Dot11FreqUsed, %Dot11LoadingRadio, %Dot11NoSSID, %Dot11Retrys, %Dot11Roamed, %DuplexMismatched, %DuplicateAddress, %ExcessiveCollision, %Flapping, %ICMP_Echo_Rep, %ICMP_Echo_Req, %InterfaceError, %InterfaceState, %InvalidMulticast, %LateCollision, %LineProtocolInterfaceState, %LoginFail, %LostCarrier, %MLSFlowmaskChanged, %MLSMcastStatus, %MLSMultiLayerDisabled, %MLSMultiLayerEnabled, %MLSNetflowDisabled, %MLSNetflowEnabled, %MgmtResetMindown, %ModemChange, %NTPpeerReach, %NTPpeerSync, %NTPpeerUnreach, %OtherList, %PORT_IN_ERRORS, %PORT_SSUPOK, %PortStateBridge, %RSHELLFail, %RSPSlaveChange, %RSPSlaveDown, %RSPSlaveUp, %RUNCFGEnabled, %ReloadRequested, %Restarted, %SNMPColdStart, %SSH, %SYSCpuHog, %SYSImgSyncFinished, %SYSImgSyncStart, %SYSModuleInserted, %SYSModuleMinorFail, %SYSModuleOk, %SYSModulePowerOn, %SYSModulePowerSupplyUp, %SYSModuleReset, %SYSModuleStandby, %SYSSupervisorEngineReset, %SYSSupervisorErr, %SYSWaitOnline, %SYSWarn, %SpantreeFailure, %StartConfigChange, %Started, %TRUNKPORTOFF, %TRUNKPORTON, %Underflow, %VLANMismatched, %drophttphost, %dropigmp, %dropsmtphost, ); # Avoid "Use of uninitialized value" warning messages. sub ValueOrDefault { my ($value, $default) = @_; return ($value ? $value : $default); } if ( $Debug >= 5 ) { print STDERR "\n\nDEBUG: Inside CISCO Filter \n\n"; $DebugCounter = 1; } my ($month,$day,$time,$host,$process,$conn,$msg); while (defined(my $ThisLine = )) { if ( $Debug >= 30 ) { print STDERR "DEBUG($DebugCounter): $ThisLine"; $DebugCounter++; } ($month,$day,$time,$host,$process,$conn,$msg)=split(/ +/,$ThisLine,7); if ( ($ThisLine =~ /(ISDN-6-.+)/ ) or ($ThisLine =~ /Copyright/ ) or ($ThisLine =~ /Cisco Internetwork Operating System Software/ ) or ($ThisLine =~ /IOS \(tm\)/ ) or ($ThisLine =~ /Cisco IOS/ ) or ($ThisLine =~ /Technical Support/ ) or ($ThisLine =~ /self test passed/ ) or ($ThisLine =~ /TAC:Home:SW:IOS:Specials/ ) ) { # don't care about this, will code this later } elsif ( $ThisLine =~ /%SEC-6-IPACCESSLOG(|D|N)P/) { $testline = $ThisLine; chomp $testline; $testline =~ s/^.*SEC-6-IPACCESSLOG(|D|N)P: list //; $testline =~ s/ ->//; $testline =~ s/, / /; $testline =~ s/ packets//; $testline =~ s/ packet//; @testfields = split(/ /,$testline); $accesslist = @testfields[0]; $action = @testfields[1]; $protocol = @testfields[2]; if ($protocol =~ /(tcp|udp)/) { $source = @testfields[3]; $destination = @testfields[4]; $icmp_type = ""; $count = @testfields[5]; @sfields = split(/\(/, $source); $source_ip = @sfields[0]; $source_port = @sfields[1]; $source_port =~ s/\)//; @dfields = split(/\(/, $destination); $destination_ip = @dfields[0]; $destination_port = @dfields[1]; $destination_port =~ s/\)//; } elsif ($protocol =~ /icmp/) { $source_ip = @testfields[3]; $source_port = 0; $destination_ip = @testfields[4]; $destination_port = 0; $icmp_type = @testfields[5]; $count = @testfields[6]; } elsif ($protocol =~ /41/) { $source_ip = @testfields[3]; $source_port = 0; $destination_ip = @testfields[4]; $destination_port = 0; $icmp_type = ""; $count = @testfields[5]; } else { $count = 0; } $ACL{$accesslist} += $count; $ACTION{$action} += $count; $packets += $count; if ( ($destination_port == 22) and ($protocol =~ /tcp/) ) { $SSH{$source_ip} += $count; $SSH_packets += $count; } } elsif ( $ThisLine =~ /%IPV6-6-ACCESSLOG(|D|N)P/) { $testline = $ThisLine; chomp $testline; $testline =~ s/^.*IPV6-6-ACCESSLOG(|D|N)P: list //; $testline =~ s/ ->//; $testline =~ s/, / /; $testline =~ s/ packets//; $testline =~ s/ packet//; @testfields = split(/ /,$testline); $accesslist = @testfields[0]; $action = @testfields[1]; $protocol = @testfields[2]; if ($protocol =~ /(tcp|udp)/) { $source = @testfields[3]; $destination = @testfields[4]; $icmp_type = ""; $count = @testfields[5]; @sfields = split(/\(/, $source); $source_ip = @sfields[0]; $source_port = @sfields[1]; $source_port =~ s/\)//; @dfields = split(/\(/, $destination); $destination_ip = @dfields[0]; $destination_port = @dfields[1]; $destination_port =~ s/\)//; } elsif ($protocol =~ /icmpv6/) { $source_ip = @testfields[3]; $source_port = 0; $destination_ip = @testfields[4]; $destination_port = 0; $icmp_type = @testfields[5]; $count = @testfields[6]; } else { $count = 0; } $ACL{$accesslist} += $count; $ACTION{$action} += $count; $IPV6_packets += $count; if ( ($destination_port == 22) and ($protocol =~ /tcp/) ) { $SSH{$source_ip} += $count; $SSH_packets += $count; } } elsif ( ($protocol,$source,$destination) = ($ThisLine =~ /%FW-6-DROP_PKT: Dropping (\S+) pkt (\S+) => (\S+)/) ) { @sfields = split(/:/, $source); $source_ip = @sfields[0]; $source_port = @sfields[1]; @dfields = split(/:/, $destination); $destination_ip = @dfields[0]; $destination_port = @dfields[1]; if ($source_port == 25) { $dropsmtphost{$destination_ip}++; $dropsmtppkts++; } if ($destination_port == 25) { $dropsmtphost{$source_ip}++; $dropsmtppkts++; } if ($source_port == 80) { $drophttphost{$source_ip}++; $drophttppkts++; } if ($destination_port == 80) { $drophttphost{$destination_ip}++; $drophttppkts++; } $InspectDrop++; } elsif ( ($ACL,$source,$count) = ($ThisLine =~ /SEC-6-IPACCESSLOGSP: list (.*) denied igmp (.*) -> 224.0.0.1 \(17\), (.*) packet(|s)/) ) { $dropigmp{$host}{$ACL}{$source} += $count; } elsif ($ThisLine =~ /%FW-3-HTTP_JAVA_BLOCK/) { $JavaBlock++; } elsif ( ($username,$vty,$address) = ($ThisLine =~ /%SYS-5-CONFIG_I: Configured from console by (\S+) on (\S+) \((\S+)\)/) ) { $Configured{$host}{"Configured from $vty by $username at ".LookupIP($address)}++; } elsif ( ($username,$vty) = ($ThisLine =~ /%SYS-5-CONFIG_I: Configured from console by (\S+) on (\S+)/) ) { $Configured{$host}{"Configured from $vty by $username"}++; } elsif ( ($username,$vty) = ($ThisLine =~ /%SYS-5-CONFIG_I: Configured from console by (\S+) \((\S+)\)/) ) { $Configured{$host}{"Configured from ".LookupIP($vty)." by $username"}++; } elsif ( ($file,$vty) = ($ThisLine =~ /%SYS-5-CONFIG_I: Configured from (\S+) by (console)/) ) { $Configured{$host}{"Configured from ".$vty." using $file"}++; } elsif ( ($unmatched) = ($ThisLine =~ /%SYS-5-CONFIG_I: (.+)/) ) { $Configured{$host}{"UNMATCHED: $unmatched"}++; } elsif ( ($unmatched) = ($ThisLine =~ /%AUDIT-5-RUN_CONFIG/) ) { $ConfigChange{$host}++; } elsif ( ($interface,$errortype,$withwho) = ($ThisLine =~ /duplex mismatch discovered on (.+) \(.*\), with (.*)/) ) { $DuplexMismatched{$host}{$interface." with ".$errortype}++; } elsif ( ($interface,$vlan_number,$withwho) = ($ThisLine =~ /Native VLAN mismatch discovered on (.+) \(([^ ]+)\), with ([^ ]+)/) ) { $VLANMismatched{$host}{$interface." vlan ".$vlan_number}++; } elsif ( ($interface) = ($ThisLine =~ /NVLANMISMATCH:Native vlan mismatch detected on port (.*)/) ) { $VLANMismatched{$host}{$interface}++; } elsif ( ($interface,$state) = ($ThisLine =~ /Line protocol on Interface (.+), changed state to (.*)/) ) { $LineProtocolInterfaceState{$host}{$interface." ".$state}++; } elsif ( ($interface,$state) = ($ThisLine =~ /Interface (.+), changed state to (.*)/) ) { $InterfaceState{$host}{$interface." ".$state}++; } elsif ( ($interface_experiencing_error) = ($ThisLine =~ /ERROR: (.*) is experiencing errors/) ) { $InterfaceError{$host}{$interface_experiencing_error}++; } elsif ( ($interface) = ($ThisLine =~ /DUPLEXMISMATCH:Full\/half duplex mismatch detected on port (.*)/) ) { $DuplexMismatched{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /ETHCNTR-3-HALF_DUX_COLLISION_EXCEED_THRESHOLD: (.*)/) ) { $DuplexMismatched{$host}{$interface}++; } elsif ( ($interface,$state,$destination_port) = ($ThisLine =~ /PORTFROMSTP:Port (.+) (.+) bridge port (.*)/) ) { $PortStateBridge{$host}{$interface." ".$state." ".$destination_port}++; } elsif ( ($interface,$state,$destination_port) = ($ThisLine =~ /PORTTOSTP:Port (.+) (.+) bridge port (.*)/) ) { $PortStateBridge{$host}{$interface." ".$state." ".$destination_port}++; } elsif ( ($Unit) = ($ThisLine =~ /Unit (.*), excessive modem control changes/) ) { $ModemChange{$host}{$Unit}++; } elsif ( ($ThisLine =~ /Compiled/) ) { $Started{$host}++; } elsif ( ($message) = ($ThisLine =~ /RELOAD: (.*)/) ) { $ReloadRequested{$host}{$message}++; } elsif ( ($message) = ($ThisLine =~ /RESTART: (.*)/) ) { $Restarted{$host}{$message}++; } elsif ( ($interface) = ($ThisLine =~ /LOSTCARR: (.*)/) ) { $LostCarrier{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /DUPADDR: (.*)/) ) { $DuplicateAddress{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /TRUNKPORTON:Port (.*)/) ) { $TRUNKPORTON{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /TRUNKPORTOFF:Port (.*)/) ) { $TRUNKPORTOFF{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-3-PORT_IN_ERRORS:(.*)/) ) { $PORT_IN_ERRORS{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-PORT_SSUPOK:(.*)/) ) { $PORT_SSUPOK{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /EXCESSCOLL: (.*)/) ) { $ExcessiveCollision{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /LATECOLL: (.*)/) ) { $LateCollision{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /UNDERFLO: (.*)/) ) { $Underflow{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-4-P2_WARN: (.*)/) ) { $SYSWarn{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /P2_WARN: (.*)/) ) { $InvalidMulticast{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /LINK_FLAP: (.*)/) ) { $Flapping{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /ERR_DISABLE: (.*)/) ) { $Flapping{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /LOGIN_FAIL:User (.*)/) ) { $LoginFail{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /RSHPORTATTEMPT: (.*)/) ) { $RSHELLFail{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SPANTREE.+: (.*)/) ) { $SpantreeFailure{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /DHCPD-4-DECLINE_CONFLICT: (.*)/) ) { $DHCPConflict{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /DHCPD-4-PING_CONFLICT: (.*)/) ) { $DHCPPingConflict{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /AAAA-4-SERVUNDEF: (.*)/) ) { $AAAServerUndef{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /AAAA-3-BADREG: (.*)/) ) { $AAABadReg{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MGMT-5-NVRAM_MINDOWN: (.*)/) ) { $MgmtResetMindown{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /RUNCFGSYNC-6-SYNCEVENT: (.*)/) ) { $RUNCFGEnabled{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /RSP-3-SLAVECHANGE: (.*)/) ) { $RSPSlaveChange{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /RSP-3-SLAVEUP: (.*)/) ) { $RSPSlaveUp{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /RSP-3-SLAVEDOWN: (.*)/) ) { $RSPSlaveDown{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MLS-5-MLSENABLED: (.*)/) ) { $MLSMultiLayerEnabled{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MLS-5-MLSDISABLED: (.*)/) ) { $MLSMultiLayerDisabled{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MLS-5-MCAST_STATUS: (.*)/) ) { $MLSMcastStatus{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MLS-5-NDEENABLED: (.*)/) ) { $MLSNetflowEnabled{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MLS-5-NDEDISABLED: (.*)/) ) { $MLSNetflowDisabled{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /MLS-5-FLOWMASKCHANGE: (.*)/) ) { $MLSFlowmaskChanged{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-3-CPUHOG: (.*)/) ) { $SYSCpuHog{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-MOD_PWRON:Module (.+) (.*)/) ) { $SYSModulePowerOn{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-1-SYS_ENABLEPS: (.*)/) ) { $SYSModulePowerSupplyUp{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-MOD_OK:Module (.+) (.*)/) ) { $SYSModuleOk{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-MOD_INSERT:Module (.+) (.*)/) ) { $SYSModuleInserted{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-3-MOD_MINORFAIL:Module (.+) (.*)/) ) { $SYSModuleMinorFail{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-MOD_RESET:Module (.+) (.*)/) ) { $SYSModuleReset{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-SUP_MODSBY:(.*)/) ) { $SYSModuleStandby{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-3-SUP_WAITSBYSUPONLINE: (.*)/) ) { $SYSWaitOnline{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-3-SUP_THISSUPRESET: (.*)/) ) { $SYSSupervisorEngineReset{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-SUP_IMGSYNCSTART: (.*)/) ) { $SYSImgSyncStart{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-5-SUP_IMGSYNCFINISH: (.*)/) ) { $SYSImgSyncFinished{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SYS-4-SUPERVISOR_ERR:Forwarding engine IP (checksum|length|too short) error counter (.*)/) ) { $SYSSupervisorErr{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /SNMP-5-COLDSTART: (.*)/) ) { $SNMPColdStart{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /COUNTERS: (.*)/) ) { $CountersMsg{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /DOT11-4-MAXRETRIES: Packet to client ....\.....\..... reached(.*)/) ) { $Dot11Retrys{$host}{$interface}++; } elsif ( ($radio,$interface) = ($ThisLine =~ /DOT11-6-ASSOC: Interface (.*), Station +(.*)/) ) { $Dot11Assoc{$host}{"$radio $interface"}++; } elsif ( ($radio,$interface) = ($ThisLine =~ /DOT11-6-DISASSOC: Interface (.*), Deauthenticating Station (.*)/) ) { $Dot11Disassoc{$host}{"$radio $interface"}++; } elsif ( ($interface) = ($ThisLine =~ /DOT11-6-ROAMED: Station (.*)/) ) { $Dot11Roamed{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /DOT11-4-NO_SSID: (.*)/) ) { $Dot11NoSSID{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /DOT11-6-FREQ_USED: (.*)/) ) { $Dot11FreqUsed{$host}{$interface}++; } elsif ( ($radio,$interface) = ($ThisLine =~ /DOT11-4-LOADING_RADIO: Interface (.*), loading the radio firmware (.*)/) ) { $Dot11LoadingRadio{$host}{"$radio $interface"}++; } elsif ( ($interface) = ($ThisLine =~ /ICMP Echo Req: (.*)/) ) { $ICMP_Echo_Req{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /ICMP Echo Rply: (.*)/) ) { $ICMP_Echo_Rep{$host}{$interface}++; } elsif ( ($interface) = ($ThisLine =~ /AMDP2_FE-5-COLL: (.*)/) ) { $ExcessiveCollision{$host}{$interface}++; } elsif ( ($hash) = ($ThisLine =~ /AUDIT-5-STARTUP_CONFIG: Startup Configuration changed. Hash: (.*)/) ) { $StartConfigChange{$host}++; } elsif ( ($ntppear) = ($ThisLine =~ /NTP-4-PEERUNREACH: Peer (.*) is unreachable/) ) { $NTPpeerUnreach{$host}{$ntppear}++; } elsif ( ($ntppear) = ($ThisLine =~ /NTP-5-PEERSYNC: NTP synced to peer (.*)/) ) { $NTPpeerSync{$host}{$ntppear}++; } elsif ( ($ntppear) = ($ThisLine =~ /NTP-6-PEERREACH: Peer (.*) is reachable/) ) { $NTPpeerReach{$host}{$ntppear}++; } elsif ( ($count) = ($ThisLine =~ /SEC-6-IPACCESSLOGRL: access-list logging rate-limited or missed (.*) packet(|s)/) ) { $ACLmissed{$host} += $count; } elsif ( $CatchUnknown >= 1 ) { # Report any unmatched entries... chomp $ThisLine; ($msg) = ($ThisLine =~ /(%.*)/); $msg ||= $ThisLine; $OtherList{"$host: $msg"}++; } } if (keys %Started) { print "\nDevice started :\n"; foreach my $ThisOne (sort keys %Started) { print " " . $ThisOne . ":\n"; print "\t Started: " . $Started{$ThisOne} . " Time(s)\n"; } } if (keys %Restarted) { print "\nDevice restarted :\n"; foreach my $ThisOne (sort keys %Restarted) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Restarted{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Restarted{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ReloadRequested) { print "\nDevice reload requested :\n"; foreach my $ThisOne (sort keys %ReloadRequested) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$ReloadRequested{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ReloadRequested{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %DuplexMismatched) { print "\nDuplex Mismatch warning:\n"; foreach my $ThisOne (sort keys %DuplexMismatched) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$DuplexMismatched{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $DuplexMismatched{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %VLANMismatched) { print "\nNative VLAN mismatch warning:\n"; foreach my $ThisOne (sort keys %VLANMismatched) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$VLANMismatched{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $VLANMismatched{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %TRUNKPORTON) { print "\nPort/Interface trunk on :\n"; foreach my $ThisOne (sort keys %TRUNKPORTON) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$TRUNKPORTON{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $TRUNKPORTON{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %TRUNKPORTOFF) { print "\nPort/Interface trunk off :\n"; foreach my $ThisOne (sort keys %TRUNKPORTOFF) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$TRUNKPORTOFF{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $TRUNKPORTOFF{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %PORT_IN_ERRORS) { print "\nPort/Interface in errors :\n"; foreach my $ThisOne (sort keys %PORT_IN_ERRORS) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$PORT_IN_ERRORS{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $PORT_IN_ERRORS{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %PORT_SSUPOK) { print "\nPort in Standy Mode :\n"; foreach my $ThisOne (sort keys %PORT_SSUPOK) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$PORT_SSUPOK{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $PORT_SSUPOK{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %PortStateBridge) { print "\nPort/Interface left/joined bridge :\n"; foreach my $ThisOne (sort keys %PortStateBridge) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$PortStateBridge{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $PortStateBridge{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Flapping) { print "\nPort/Interface Flapping :\n"; foreach my $ThisOne (sort keys %Flapping) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Flapping{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $Flapping{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %DuplicateAddress) { print "\nPort/Interface duplicate address :\n"; foreach my $ThisOne (sort keys %DuplicateAddress) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$DuplicateAddress{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $DuplicateAddress{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %InvalidMulticast) { print "\nPort/Interface invalid multicast :\n"; foreach my $ThisOne (sort keys %InvalidMulticast) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$InvalidMulticast{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $InvalidMulticast{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SpantreeFailure) { print "\nPort/Interface spantree failure :\n"; foreach my $ThisOne (sort keys %SpantreeFailure) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SpantreeFailure{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SpantreeFailure{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if ($Detail >= 5 && keys %LineProtocolInterfaceState) { print "\nLine protocol on Port/Interface changed state :\n"; foreach my $ThisOne (sort keys %LineProtocolInterfaceState) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$LineProtocolInterfaceState{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $LineProtocolInterfaceState{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (( $Detail >= 5 ) and (keys %InterfaceState)) { print "\nPort/Interface state change :\n"; foreach my $ThisOne (sort keys %InterfaceState) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$InterfaceState{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $InterfaceState{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Underflow) { print "\nPort/Interface transmit error(underflow) :\n"; foreach my $ThisOne (sort keys %Underflow) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Underflow{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $Underflow{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %LostCarrier) { print "\nPort/Interface transmit error (lost carrier) :\n"; foreach my $ThisOne (sort keys %LostCarrier) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$LostCarrier{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $LostCarrier{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %LateCollision) { print "\nPort/Interface transmit error (Late collision) :\n"; foreach my $ThisOne (sort keys %LateCollision) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$LateCollision{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $LateCollision{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ExcessiveCollision) { print "\nPort/Interface Excessive collision :\n"; foreach my $ThisOne (sort keys %ExcessiveCollision) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$ExcessiveCollision{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $ExcessiveCollision{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %InterfaceError) { print "\nPort/Interface experiencing error :\n"; foreach my $ThisOne (sort keys %InterfaceError) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$InterfaceError{$ThisOne}}) { print "\tPort or Interface " .$ThatOne . "\t: " . $InterfaceError{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ModemChange) { print "\nExcessive modem control changes:\n"; foreach my $ThisOne (sort keys %ModemChange) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$ModemChange{$ThisOne}}) { print "\tUnit " .$ThatOne . "\t: " . $ModemChange{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %CountersMsg) { print "\nCounters changes:\n"; foreach my $ThisOne (sort keys %CountersMsg) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$CountersMsg{$ThisOne}}) { print "\t" .$ThatOne . "\t: " . $CountersMsg{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Configured) { print "\nDevice configured by :\n"; foreach my $ThisOne (sort keys %Configured) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Configured{$ThisOne}}) { print "\t" .$ThatOne . "\t: " . $Configured{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ConfigChange) { print "\nDevice config changes :\n"; foreach my $ThisOne (sort keys %ConfigChange) { print " " . $ThisOne . ": " . $ConfigChange{$ThisOne} . " Time(s)\n"; } } if (keys %StartConfigChange) { print "\nDevice start config changes :\n"; foreach my $ThisOne (sort keys %StartConfigChange) { print " " . $ThisOne . ": " . $StartConfigChange{$ThisOne} . " Time(s)\n"; } } if (keys %LoginFail) { print "\nLogin failed on device :\n"; foreach my $ThisOne (sort keys %LoginFail) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$LoginFail{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $LoginFail{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %RSHELLFail) { print "\nRemote Shell Login failed on device :\n"; foreach my $ThisOne (sort keys %RSHELLFail) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$RSHELLFail{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $RSHELLFail{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %DHCPConflict) { print "\nDHCP Conflict on device :\n"; foreach my $ThisOne (sort keys %DHCPConflict) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$DHCPConflict{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $DHCPConflict{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %DHCPPingConflict) { print "\nDHCP Address Conflict on device :\n"; foreach my $ThisOne (sort keys %DHCPPingConflict) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$DHCPPingConflict{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $DHCPPingConflict{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %AAAServerUndef) { print "\nAAA Server Undefined on device :\n"; foreach my $ThisOne (sort keys %AAAServerUndef) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$AAAServerUndef{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $AAAServerUndef{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %AAABadReg) { print "\nAAA Bad Register on device :\n"; foreach my $ThisOne (sort keys %AAABadReg) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$AAABadReg{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $AAABadReg{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %RUNCFGEnabled) { print "\nHigh-Availability Redundancy Feature is enabled on device :\n"; foreach my $ThisOne (sort keys %RUNCFGEnabled) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$RUNCFGEnabled{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $RUNCFGEnabled{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %RSPSlaveUp) { print "\nHigh-Availability Redundancy Feature, Card is manually reset, Slave is up on device :\n"; foreach my $ThisOne (sort keys %RSPSlaveUp) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$RSPSlaveUp{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $RSPSlaveUp{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %RSPSlaveDown) { print "\nHigh-Availability Redundancy Feature, Card is manually reset, Slave is down on device :\n"; foreach my $ThisOne (sort keys %RSPSlaveDown) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$RSPSlaveDown{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $RSPSlaveDown{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %RSPSlaveChange) { print "\nHigh-Availability Redundancy Feature, Card is manually reset, Slave is changing state on device :\n"; foreach my $ThisOne (sort keys %RSPSlaveChange) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$RSPSlaveChange{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $RSPSlaveChange{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MLSFlowmaskChanged) { print "\nFlow Mask Changed on device :\n"; foreach my $ThisOne (sort keys %MLSFlowmaskChanged) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MLSFlowmaskChanged{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MLSFlowmaskChanged{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MLSMcastStatus) { print "\nIP Multicast status on device :\n"; foreach my $ThisOne (sort keys %MLSMcastStatus) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MLSMcastStatus{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MLSMcastStatus{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MLSMultiLayerEnabled) { print "\nIP Multilayer switching enabled on device :\n"; foreach my $ThisOne (sort keys %MLSMultiLayerEnabled) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MLSMultiLayerEnabled{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MLSMultiLayerEnabled{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MLSMultiLayerDisabled) { print "\nIP Multilayer switching disabled on device :\n"; foreach my $ThisOne (sort keys %MLSMultiLayerDisabled) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MLSMultiLayerDisabled{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MLSMultiLayerDisabled{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MLSNetflowEnabled) { print "\nNetflow enabled on device :\n"; foreach my $ThisOne (sort keys %MLSNetflowEnabled) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MLSNetflowEnabled{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MLSNetflowEnabled{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MLSNetflowDisabled) { print "\nNetflow disabled on device :\n"; foreach my $ThisOne (sort keys %MLSNetflowDisabled) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MLSNetflowDisabled{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MLSNetflowDisabled{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %MgmtResetMindown) { print "\nReset mindown on device :\n"; foreach my $ThisOne (sort keys %MgmtResetMindown) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$MgmtResetMindown{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $MgmtResetMindown{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSWarn) { print "\nModule timeout on device :\n"; foreach my $ThisOne (sort keys %SYSWarn) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSWarn{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSWarn{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSCpuHog) { print "\nCpu Hog on device :\n"; foreach my $ThisOne (sort keys %SYSCpuHog) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSCpuHog{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSCpuHog{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModulePowerSupplyUp) { print "\nModule power supply up on device :\n"; foreach my $ThisOne (sort keys %SYSModulePowerSupplyUp) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModulePowerSupplyUp{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSModulePowerSupplyUp{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModulePowerOn) { print "\nModule power up on device :\n"; foreach my $ThisOne (sort keys %SYSModulePowerOn) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModulePowerOn{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSModulePowerOn{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModuleOk) { print "\nModule online on device :\n"; foreach my $ThisOne (sort keys %SYSModuleOk) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModuleOk{$ThisOne}}) { print "\t Module " .$ThatOne . "\t: " . $SYSModuleOk{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModuleInserted) { print "\nModule inserted on device :\n"; foreach my $ThisOne (sort keys %SYSModuleInserted) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModuleInserted{$ThisOne}}) { print "\t Module " .$ThatOne . "\t: " . $SYSModuleInserted{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModuleMinorFail) { print "\nModule minor failure on device :\n"; foreach my $ThisOne (sort keys %SYSModuleMinorFail) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModuleMinorFail{$ThisOne}}) { print "\t Module " .$ThatOne . "\t: " . $SYSModuleMinorFail{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModuleReset) { print "\nModule reset on device :\n"; foreach my $ThisOne (sort keys %SYSModuleReset) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModuleReset{$ThisOne}}) { print "\t Module " .$ThatOne . "\t: " . $SYSModuleReset{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSWaitOnline) { print "\nSupervisor engine in the process on being online on device :\n"; foreach my $ThisOne (sort keys %SYSWaitOnline) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSWaitOnline{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSWaitOnline{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSModuleStandby) { print "\nModule standby on device :\n"; foreach my $ThisOne (sort keys %SYSModuleStandby) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSModuleStandby{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSModuleStandby{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSSupervisorEngineReset) { print "\nSupervisor engine reset on device :\n"; foreach my $ThisOne (sort keys %SYSSupervisorEngineReset) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSSupervisorEngineReset{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSSupervisorEngineReset{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSImgSyncStart) { print "\nSwitch of supervisor engine started :\n"; foreach my $ThisOne (sort keys %SYSImgSyncStart) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSImgSyncStart{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSImgSyncStart{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSSupervisorErr) { print "\nForwarding engine IP error :\n"; foreach my $ThisOne (sort keys %SYSSupervisorErr) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSSupervisorErr{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSSupervisorErr{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SYSImgSyncFinished) { print "\nSwitch of supervisor engine finished :\n"; foreach my $ThisOne (sort keys %SYSImgSyncFinished) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SYSImgSyncFinished{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SYSImgSyncFinished{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %SNMPColdStart) { print "\nSNMP Cold (Re)start on device :\n"; foreach my $ThisOne (sort keys %SNMPColdStart) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$SNMPColdStart{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $SNMPColdStart{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Dot11Retrys) { print "\nAccess Point Dot11 Max retries on device :\n"; foreach my $ThisOne (sort keys %Dot11Retrys) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11Retrys{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11Retrys{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if ($Detail >= 5 && keys %Dot11Assoc) { print "\nAccess Point Dot11 associated with device :\n"; foreach my $ThisOne (sort keys %Dot11Assoc) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11Assoc{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11Assoc{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if ($Detail >= 5 && keys %Dot11Disassoc) { print "\nAccess Point Dot11 disassociated with device :\n"; foreach my $ThisOne (sort keys %Dot11Disassoc) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11Disassoc{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11Disassoc{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Dot11Roamed) { print "\nAccess Point Dot11 roaming with device :\n"; foreach my $ThisOne (sort keys %Dot11Roamed) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11Roamed{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11Roamed{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Dot11NoSSID) { print "\nAccess Point Dot11 no SSID with device :\n"; foreach my $ThisOne (sort keys %Dot11NoSSID) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11NoSSID{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11NoSSID{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Dot11FreqUsed) { print "\nAccess Point Dot11 Frequency selected with device :\n"; foreach my $ThisOne (sort keys %Dot11FreqUsed) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11FreqUsed{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11FreqUsed{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %Dot11LoadingRadio) { print "\nAccess Point Dot11 Loading Radio firmware with device :\n"; foreach my $ThisOne (sort keys %Dot11LoadingRadio) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$Dot11LoadingRadio{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $Dot11LoadingRadio{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ICMP_Echo_Req) { print "\nICMP Echo Request on device :\n"; foreach my $ThisOne (sort keys %ICMP_Echo_Req) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$ICMP_Echo_Req{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ICMP_Echo_Req{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %NTPpeerSync) { print "\nNTP peer synced on device :\n"; foreach my $ThisOne (sort keys %NTPpeerSync) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$NTPpeerSync{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $NTPpeerSync{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %NTPpeerReach) { print "\nNTP peer reachable on device :\n"; foreach my $ThisOne (sort keys %NTPpeerReach) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$NTPpeerReach{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $NTPpeerReach{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %NTPpeerUnreach) { print "\nNTP peer unreachable on device :\n"; foreach my $ThisOne (sort keys %NTPpeerUnreach) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$NTPpeerUnreach{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $NTPpeerUnreach{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ACLmissed) { print "\nACL logging missed/rate-limited on device :\n"; foreach my $ThisOne (sort keys %ACLmissed) { print " " . $ThisOne . " : " . $ACLmissed{$ThisOne} . " Time(s)\n"; } } if (keys %dropigmp) { print "\nACL IGMP on device :\n"; foreach my $ThisOne (sort keys %dropigmp) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$dropigmp{$ThisOne}}) { print "\t " .$ThatOne . "\t:\n"; foreach my $WhichOne (sort keys %{$dropigmp{$ThisOne}{$ThatOne}}) { print "\t " .$WhichOne . "\t: " . $dropigmp{$ThisOne}{$ThatOne}{$WhichOne} . " Time(s)\n"; } } } } if (keys %ICMP_Echo_Rep) { print "\nICMP Echo Reply on device :\n"; foreach my $ThisOne (sort keys %ICMP_Echo_Rep) { print " " . $ThisOne . ":\n"; foreach my $ThatOne (sort keys %{$ICMP_Echo_Rep{$ThisOne}}) { print "\t " .$ThatOne . "\t: " . $ICMP_Echo_Rep{$ThisOne}{$ThatOne} . " Time(s)\n"; } } } if (keys %ACL) { print "\nAccess Control Lists:\n"; foreach my $ThisOne (sort {($a =~/\/(\d+)/)[0] <=> ($b =~ /\/(\d+)/)[0]} keys %ACL) { print " " . $ThisOne . " : " . $ACL{$ThisOne} . " Hit(s)\n"; } print " Total : " . $packets . " Hit(s)\n"; print " IPv6 Total : " . $IPV6_packets . " Hit(s)\n"; } if (keys %ACTION) { print "\nActions:\n"; foreach my $ThisOne (sort keys %ACTION) { print " " . $ThisOne . " : " . $ACTION{$ThisOne} . " Hit(s)\n"; } print " Total : " . $packets . " Hit(s)\n"; print " IPv6 Total : " . $IPV6_packets . " Hit(s)\n"; } if ($InspectDrop > 0) { print "\nInspect rule drops : $InspectDrop\n"; } if (keys %dropsmtphost) { print " SMTP servers:\n"; foreach my $ThisOne (sort SortIP keys %dropsmtphost) { if ($dropsmtphost{$ThisOne} > 1) { print " " . $ThisOne . " : " . $dropsmtphost{$ThisOne} . " Drops\n"; } } print " Total : " . $dropsmtppkts . "\n"; } if (keys %drophttphost) { print " HTTP servers:\n"; foreach my $ThisOne (sort SortIP keys %drophttphost) { if ($drophttphost{$ThisOne} > 1) { print " " . $ThisOne . " : " . $drophttphost{$ThisOne} . " Drop(s)\n"; } } print " Total : " . $drophttppkts . "\n"; } if ($JavaBlock > 0) { print "\nJAVA applet(s) blocked : $JavaBlock\n"; } if (keys %SSH) { print "\nSSH access:\n"; foreach my $ThisOne (sort SortIP keys %SSH) { print " " . $ThisOne . " : " . $SSH{$ThisOne} . " Hit(s)\n"; } print " Total : " . $SSH_packets . " Hit(s)\n"; } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; $lines = 0; $count = 0; foreach $line (sort keys %OtherList) { if ($OtherList{$line} > 1 || $Detail >= 5) { print " $line: $OtherList{$line} Time(s)\n"; } else { $lines++; $count += $OtherList{$line}; } } if ($lines) { print " $count messages from $lines lines suppressed\n"; } } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/qmail-send0000644000175000017500000004773115075123620020763 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2008 Bob Hutchinson ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'}; my $QmailDetail = $ENV{'qmail_high_detail'}; my $QmailThreshold = $ENV{'threshold'}; my $RemoteThreshold = $ENV{'remote_threshold'}; my $LocalThreshold = $ENV{'local_threshold'}; my $FromThreshold = $ENV{'from_threshold'}; #Init String Container my ( $DeliveryResponse, $EmailFrom, $IgnoreUnmatched, $Response, $ResponseCode, $ServerResponseOverallTotal, $ServerResponses, $ToLocal, $ToRemote, $ct, $msgid, $percentage, $threshold_reached, $triplebounce, $warning, ); #Init Hashes my ( %From, %Local, %Remote, %ServerResponseTotal, %TripleBounce, %Warning, ); #Init Arrays my @OtherList = (); #Init Info Hashes my %SmtpCode; my %ErrorCode; $SmtpCode{'211'} = "System status, or system help reply."; $SmtpCode{'214'} = "Help message."; $SmtpCode{'220'} = "Domain service ready. Ready to start TLS."; $SmtpCode{'221'} = "Domain service closing transmission channel."; $SmtpCode{'250'} = "Requested mail action OK, completed."; $SmtpCode{'251'} = "User not local, will forward to forwardpath."; $SmtpCode{'252'} = "Cannot VRFY user, but will take message for this user and attempt delivery."; $SmtpCode{'253'} = "OK, messages pending messages for node node started."; $SmtpCode{'354'} = "Start mail input; end with .."; $SmtpCode{'355'} = "Octet-offset is the transaction offset."; $SmtpCode{'421'} = "Domain service not available, closing transmission channel."; $SmtpCode{'432'} = "A password transition is needed."; $SmtpCode{'450'} = "Requested mail action not taken: mailbox unavailable. ATRN request refused."; $SmtpCode{'451'} = "Requested action aborted: local error in processing. Unable to process ATRN request now"; $SmtpCode{'452'} = "Requested action not taken: insufficient system storage."; $SmtpCode{'453'} = "You have no mail."; $SmtpCode{'454'} = "TLS not available due to temporary reason. Encryption required for requested authentication mechanism."; $SmtpCode{'458'} = "Unable to queue messages for node node."; $SmtpCode{'459'} = "Node node not allowed: reason."; $SmtpCode{'500'} = "Command not recognized: command Syntax error."; $SmtpCode{'501'} = "Syntax error, no parameters allowed."; $SmtpCode{'502'} = "Command not implemented."; $SmtpCode{'503'} = "Bad sequence of commands."; $SmtpCode{'504'} = "Command parameter not implemented."; $SmtpCode{'511'} = "Bad destination mailbox address"; $SmtpCode{'521'} = "Machine does not accept mail."; $SmtpCode{'530'} = "Must issue a STARTTLS command first. Encryption required for requested authentication mechanism."; $SmtpCode{'534'} = "Authentication mechanism is too weak."; $SmtpCode{'538'} = "Encryption required for requested authentication mechanism."; $SmtpCode{'550'} = "Requested action not taken: mailbox unavailable."; $SmtpCode{'551'} = "User not local; please try forwardpath."; $SmtpCode{'552'} = "Requested mail action aborted: exceeded storage allocation."; $SmtpCode{'553'} = "Requested action not taken: mailbox name not allowed."; $SmtpCode{'554'} = "Transaction failed."; #ErrorCodes # Success ## X.0.X Other or Undefined Status $ErrorCode{'2.0.1'} = "Success: Undefined Status"; $ErrorCode{'2.0.2'} = "Success: Undefined Status"; $ErrorCode{'2.0.3'} = "Success: Undefined Status"; $ErrorCode{'2.0.4'} = "Success: Undefined Status"; $ErrorCode{'2.0.5'} = "Success: Undefined Status"; $ErrorCode{'2.0.6'} = "Success: Undefined Status"; $ErrorCode{'2.0.7'} = "Success: Undefined Status"; $ErrorCode{'2.0.8'} = "Success: Undefined Status"; ## X.1.X Addressing Status $ErrorCode{'2.1.1'} = "Success: Bad destination mailbox address"; $ErrorCode{'2.1.2'} = "Success: Bad destination system address"; $ErrorCode{'2.1.3'} = "Success: Bad destination mailbox address syntax"; $ErrorCode{'2.1.4'} = "Success: Destination mailbox address ambiguous"; $ErrorCode{'2.1.5'} = "Success: Destination mailbox address valid"; $ErrorCode{'2.1.6'} = "Success: Mailbox has moved"; $ErrorCode{'2.1.7'} = "Success: Bad sender's mailbox address syntax"; $ErrorCode{'2.1.8'} = "Success: Bad sender's system address"; ## X.2.X Mailbox Status $ErrorCode{'2.2.0'} = "Success: Other or undefined mailbox status"; $ErrorCode{'2.2.1'} = "Success: Mailbox disabled, not accepting messages"; $ErrorCode{'2.2.2'} = "Success: Mailbox full"; $ErrorCode{'2.2.3'} = "Success: Message length exceeds administrative limit"; $ErrorCode{'2.2.4'} = "Success: Mailing list expansion problem"; ## X.3.X Mail System Status $ErrorCode{'2.3.0'} = "Success: Other or undefined mail system status"; $ErrorCode{'2.3.1'} = "Success: Mail system full"; $ErrorCode{'2.3.2'} = "Success: System not accepting network messages"; $ErrorCode{'2.3.3'} = "Success: System not capable of selected features"; $ErrorCode{'2.3.4'} = "Success: Message too big for system"; ## X.4.X Network and Routing Status $ErrorCode{'2.4.0'} = "Success: Other or undefined network or routing status"; $ErrorCode{'2.4.1'} = "Success: No answer from host"; $ErrorCode{'2.4.2'} = "Success: Bad connection"; $ErrorCode{'2.4.3'} = "Success: Routing server failure"; $ErrorCode{'2.4.4'} = "Success: Unable to route"; $ErrorCode{'2.4.5'} = "Success: Network congestion"; $ErrorCode{'2.4.6'} = "Success: Routing loop detected"; $ErrorCode{'2.4.7'} = "Success: Delivery time expired"; ## X.5.X Mail Delivery Protocol Status $ErrorCode{'2.5.0'} = "Success: Other or undefined protocol status"; $ErrorCode{'2.5.1'} = "Success: Invalid command"; $ErrorCode{'2.5.2'} = "Success: Syntax error"; $ErrorCode{'2.5.3'} = "Success: Too many recipients"; $ErrorCode{'2.5.4'} = "Success: Invalid command arguments"; $ErrorCode{'2.5.5'} = "Success: Wrong protocol version"; ## X.6.X Message Content or Media Status $ErrorCode{'2.6.0'} = "Success: Other or undefined media error"; $ErrorCode{'2.6.1'} = "Success: Media not supported"; $ErrorCode{'2.6.2'} = "Success: Conversion required and prohibited"; $ErrorCode{'2.6.3'} = "Success: Conversion required but not supported"; $ErrorCode{'2.6.4'} = "Success: Conversion with loss performed"; $ErrorCode{'2.6.5'} = "Success: Conversion failed"; ## X.7.X Security or Policy Status $ErrorCode{'2.7.0'} = "Success: Other or undefined security status"; $ErrorCode{'2.7.1'} = "Success: Delivery not authorized, message refused"; $ErrorCode{'2.7.2'} = "Success: Mailing list expansion prohibited"; $ErrorCode{'2.7.3'} = "Success: Security conversion required but not possible"; $ErrorCode{'2.7.4'} = "Success: Security features not supported"; $ErrorCode{'2.7.5'} = "Success: Cryptographic failure"; $ErrorCode{'2.7.6'} = "Success: Cryptographic algorithm not supported"; $ErrorCode{'2.7.7'} = "Success: Message integrity failure"; # Persistent Transient Failure ## X.0.X Other or Undefined Status $ErrorCode{'4.0.1'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.2'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.3'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.4'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.5'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.6'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.7'} = "Deferral: Undefined Status"; $ErrorCode{'4.0.8'} = "Deferral: Undefined Status"; ## X.1.X Addressing Status $ErrorCode{'4.1.1'} = "Deferral: Bad destination mailbox address"; $ErrorCode{'4.1.2'} = "Deferral: Bad destination system address"; $ErrorCode{'4.1.3'} = "Deferral: Bad destination mailbox address syntax"; $ErrorCode{'4.1.4'} = "Deferral: Destination mailbox address ambiguous"; $ErrorCode{'4.1.5'} = "Deferral: Destination mailbox address valid"; $ErrorCode{'4.1.6'} = "Deferral: Mailbox has moved"; $ErrorCode{'4.1.7'} = "Deferral: Bad sender's mailbox address syntax"; $ErrorCode{'4.1.8'} = "Deferral: Bad sender's system address"; ## X.2.X Mailbox Status $ErrorCode{'4.2.0'} = "Deferral: Other or undefined mailbox status"; $ErrorCode{'4.2.1'} = "Deferral: Mailbox disabled, not accepting messages"; $ErrorCode{'4.2.2'} = "Deferral: Mailbox full"; $ErrorCode{'4.2.3'} = "Deferral: Message length exceeds administrative limit"; $ErrorCode{'4.2.4'} = "Deferral: Mailing list expansion problem"; ## X.3.X Mail System Status $ErrorCode{'4.3.0'} = "Deferral: Other or undefined mail system status"; $ErrorCode{'4.3.1'} = "Deferral: Mail system full"; $ErrorCode{'4.3.2'} = "Deferral: System not accepting network messages"; $ErrorCode{'4.3.3'} = "Deferral: System not capable of selected features"; $ErrorCode{'4.3.4'} = "Deferral: Message too big for system"; ## X.4.X Network and Routing Status $ErrorCode{'4.4.0'} = "Deferral: Other or undefined network or routing status"; $ErrorCode{'4.4.1'} = "Deferral: No answer from host"; $ErrorCode{'4.4.2'} = "Deferral: Bad connection"; $ErrorCode{'4.4.3'} = "Deferral: Routing server failure"; $ErrorCode{'4.4.4'} = "Deferral: Unable to route"; $ErrorCode{'4.4.5'} = "Deferral: Network congestion"; $ErrorCode{'4.4.6'} = "Deferral: Routing loop detected"; $ErrorCode{'4.4.7'} = "Deferral: Delivery time expired"; ## X.5.X Mail Delivery Protocol Status $ErrorCode{'4.5.0'} = "Deferral: Other or undefined protocol status"; $ErrorCode{'4.5.1'} = "Deferral: Invalid command"; $ErrorCode{'4.5.2'} = "Deferral: Syntax error"; $ErrorCode{'4.5.3'} = "Deferral: Too many recipients"; $ErrorCode{'4.5.4'} = "Deferral: Invalid command arguments"; $ErrorCode{'4.5.5'} = "Deferral: Wrong protocol version"; ## X.6.X Message Content or Media Status $ErrorCode{'4.6.0'} = "Deferral: Other or undefined media error"; $ErrorCode{'4.6.1'} = "Deferral: Media not supported"; $ErrorCode{'4.6.2'} = "Deferral: Conversion required and prohibited"; $ErrorCode{'4.6.3'} = "Deferral: Conversion required but not supported"; $ErrorCode{'4.6.4'} = "Deferral: Conversion with loss performed"; $ErrorCode{'4.6.5'} = "Deferral: Conversion failed"; ## X.7.X Security or Policy Status $ErrorCode{'4.7.0'} = "Deferral: Other or undefined security status"; $ErrorCode{'4.7.1'} = "Deferral: Delivery not authorized, message refused"; $ErrorCode{'4.7.2'} = "Deferral: Mailing list expansion prohibited"; $ErrorCode{'4.7.3'} = "Deferral: Security conversion required but not possible"; $ErrorCode{'4.7.4'} = "Deferral: Security features not supported"; $ErrorCode{'4.7.5'} = "Deferral: Cryptographic failure"; $ErrorCode{'4.7.6'} = "Deferral: Cryptographic algorithm not supported"; $ErrorCode{'4.7.7'} = "Deferral: Message integrity failure"; # Permanent Failure ## X.0.X Other or Undefined Status $ErrorCode{'5.0.1'} = "Failure: Undefined Status"; $ErrorCode{'5.0.2'} = "Failure: Undefined Status"; $ErrorCode{'5.0.3'} = "Failure: Undefined Status"; $ErrorCode{'5.0.4'} = "Failure: Undefined Status"; $ErrorCode{'5.0.5'} = "Failure: Undefined Status"; $ErrorCode{'5.0.6'} = "Failure: Undefined Status"; $ErrorCode{'5.0.7'} = "Failure: Undefined Status"; $ErrorCode{'5.0.8'} = "Failure: Undefined Status"; ## X.1.X Addressing Status $ErrorCode{'5.1.1'} = "Failure: Bad destination mailbox address"; $ErrorCode{'5.1.2'} = "Failure: Bad destination system address"; $ErrorCode{'5.1.3'} = "Failure: Bad destination mailbox address syntax"; $ErrorCode{'5.1.4'} = "Failure: Destination mailbox address ambiguous"; $ErrorCode{'5.1.5'} = "Failure: Destination mailbox address valid"; $ErrorCode{'5.1.6'} = "Failure: Mailbox has moved"; $ErrorCode{'5.1.7'} = "Failure: Bad sender's mailbox address syntax"; $ErrorCode{'5.1.8'} = "Failure: Bad sender's system address"; ## X.2.X Mailbox Status $ErrorCode{'5.2.0'} = "Failure: Other or undefined mailbox status"; $ErrorCode{'5.2.1'} = "Failure: Mailbox disabled, not accepting messages"; $ErrorCode{'5.2.2'} = "Failure: Mailbox full"; $ErrorCode{'5.2.3'} = "Failure: Message length exceeds administrative limit"; $ErrorCode{'5.2.4'} = "Failure: Mailing list expansion problem"; ## X.3.X Mail System Status $ErrorCode{'5.3.0'} = "Failure: Other or undefined mail system status"; $ErrorCode{'5.3.1'} = "Failure: Mail system full"; $ErrorCode{'5.3.2'} = "Failure: System not accepting network messages"; $ErrorCode{'5.3.3'} = "Failure: System not capable of selected features"; $ErrorCode{'5.3.4'} = "Failure: Message too big for system"; ## X.4.X Network and Routing Status $ErrorCode{'5.4.0'} = "Failure: Other or undefined network or routing status"; $ErrorCode{'5.4.1'} = "Failure: No answer from host"; $ErrorCode{'5.4.2'} = "Failure: Bad connection"; $ErrorCode{'5.4.3'} = "Failure: Routing server failure"; $ErrorCode{'5.4.4'} = "Failure: Unable to route"; $ErrorCode{'5.4.5'} = "Failure: Network congestion"; $ErrorCode{'5.4.6'} = "Failure: Routing loop detected"; $ErrorCode{'5.4.7'} = "Failure: Delivery time expired"; ## X.5.X Mail Delivery Protocol Status $ErrorCode{'5.5.0'} = "Failure: Other or undefined protocol status"; $ErrorCode{'5.5.1'} = "Failure: Invalid command"; $ErrorCode{'5.5.2'} = "Failure: Syntax error"; $ErrorCode{'5.5.3'} = "Failure: Too many recipients"; $ErrorCode{'5.5.4'} = "Failure: Invalid command arguments"; $ErrorCode{'5.5.5'} = "Failure: Wrong protocol version"; ## X.6.X Message Content or Media Status $ErrorCode{'5.6.0'} = "Failure: Other or undefined media error"; $ErrorCode{'5.6.1'} = "Failure: Media not supported"; $ErrorCode{'5.6.2'} = "Failure: Conversion required and prohibited"; $ErrorCode{'5.6.3'} = "Failure: Conversion required but not supported"; $ErrorCode{'5.6.4'} = "Failure: Conversion with loss performed"; $ErrorCode{'5.6.5'} = "Failure: Conversion failed"; ## X.7.X Security or Policy Status $ErrorCode{'5.7.0'} = "Failure: Other or undefined security status"; $ErrorCode{'5.7.1'} = "Failure: Delivery not authorized, message refused"; $ErrorCode{'5.7.2'} = "Failure: Mailing list expansion prohibited"; $ErrorCode{'5.7.3'} = "Failure: Security conversion required but not possible"; $ErrorCode{'5.7.4'} = "Failure: Security features not supported"; $ErrorCode{'5.7.5'} = "Failure: Cryptographic failure"; $ErrorCode{'5.7.6'} = "Failure: Cryptographic algorithm not supported"; $ErrorCode{'5.7.7'} = "Failure: Message integrity failure"; while (defined(my $ThisLine = )) { if ( ( $ThisLine =~ /new msg/ ) or ( $ThisLine =~ /status: / ) or ( $ThisLine =~ /bounce msg/ ) or ( $ThisLine =~ /end msg/ ) ) { # We don't care about these } # @4000000042203bb712373f04.s:@4000000042202ab23b98282c delivery 28: deferral: Sorry,_I_wasn't_able_to_establish_an_SMTP_connection._(#4.4.1)/ elsif ( ($msgid,$DeliveryResponse,$Response) = ( $ThisLine =~ /delivery (\d+)\: (.*?)\:(.*)/ ) ) { if ( $Response =~ /did_/ ) { # ignore these. } else { if ( ($ResponseCode) = ( $Response =~ /Remote_host_said\:_(\d{3})_/ ) ) { $ServerResponses->{$DeliveryResponse}->{$ResponseCode}++; } else { #if ( $DeliveryResponse =~ /failure/ ) if ( ( $ResponseCode) = ( $ThisLine =~ /failure:.*_vpopmail_\(#(\d\.\d\.\d)\)\// ) ) { #$ResponseCode=511; $ResponseCode =~ s/\.//g; $ServerResponses->{$DeliveryResponse}->{$ResponseCode}++; } #if ( $DeliveryResponse =~ /deferral/ ) if ( ( $ResponseCode) = ( $ThisLine =~ /deferral:.*_\(#(\d\.\d\.\d)\)\// ) ) { #$ResponseCode=443; $ServerResponses->{$DeliveryResponse}->{$ResponseCode}++; } } } } elsif ( ($EmailFrom) = ( $ThisLine =~ /from \<(.*)\>/ ) ) { $From{$EmailFrom}++; } elsif ( ($ToLocal) = ( $ThisLine =~ /to local (.*)/ ) ) { $Local{$ToLocal}++; } elsif ( ($ToRemote) = ( $ThisLine =~ /to remote (.*)/ ) ) { $Remote{$ToRemote}++; } elsif( ($warning) = ($ThisLine =~ /warning: (.*)/ ) ) { $Warning{$warning}++; } elsif ( ($triplebounce) = ($ThisLine =~ /triple bounce: discarding bounce\/(\d+)$/ ) ) { $TripleBounce{$triplebounce}++; } else { # Report any unmatched entries... push @OtherList,$ThisLine; } } if ($QmailDetail >= 1) { if ($QmailThreshold > 0) { if (($RemoteThreshold < 0) or ($RemoteThreshold eq '')) { $RemoteThreshold = $QmailThreshold; } if (($FromThreshold < 0) or ($FromThreshold eq '')) { $FromThreshold = $QmailThreshold; } if (($LocalThreshold < 0) or ($LocalThreshold eq '')) { $LocalThreshold = $QmailThreshold; } } if (($RemoteThreshold < 0) or ($RemoteThreshold eq '')) { $RemoteThreshold = 0; } if (($FromThreshold < 0) or ($FromThreshold eq '')) { $FromThreshold = 0; } if (($LocalThreshold < 0) or ($LocalThreshold eq '')) { $LocalThreshold = 0; } if ( (keys %From) ) { print "\nEmails from (Threshold of " . $FromThreshold . "):\n"; $threshold_reached=0; foreach my $Line (sort {$From{$b} <=> $From{$a}} keys %From) { if ($From{$Line} >= $FromThreshold) { $threshold_reached = 1; print "\t" . $Line . " - ". $From{$Line} . " Time(s)\n"; } } if ($threshold_reached < 1) { print "\t" . "None found above the threshold ($FromThreshold)\n"; } } if ( (keys %Remote) ) { print "\nEmails to Remote Server (Threshold of " . $RemoteThreshold . "):\n"; $threshold_reached=0; foreach my $Line (sort {$Remote{$b} <=> $Remote{$a}} keys %Remote) { if ($Remote{$Line} >= $RemoteThreshold) { $threshold_reached=1; print "\t" . $Line . " - ". $Remote{$Line} . " Time(s)\n"; } } if ($threshold_reached < 1) { print "\t" . "None found above the threshold ($RemoteThreshold)\n"; } } if ( (keys %Local) ) { print "\nEmails to Local Server (Threshold of " . $LocalThreshold . "):\n"; $threshold_reached=0; foreach my $Line (sort {$Local{$b} <=> $Local{$a}} keys %Local) { if ($Local{$Line} >= $LocalThreshold) { $threshold_reached=1; print "\t" . $Line . " - ". $Local{$Line} . " Time(s)\n"; } } if ($threshold_reached < 1) { print "\t" . "None found above the threshold ($LocalThreshold)\n"; } } } if (keys %{$ServerResponses}) { print "\nRemote Server Responses:\n"; foreach my $Line (sort {$a cmp $b} keys %{$ServerResponses}) { foreach $Detail (sort {$a cmp $b} keys %{$ServerResponses->{$Line}}) { $ServerResponseTotal{$Line} += $ServerResponses->{$Line}->{$Detail}; $ServerResponseOverallTotal += $ServerResponses->{$Line}->{$Detail}; print "\t".ucfirst($Line)."(" . $Detail . ") - ". $ServerResponses->{$Line}->{$Detail} . " Time(s)"; if ( $SmtpCode{$Detail} ) { print " - ".$SmtpCode{$Detail}; } elsif ( $ErrorCode{$Detail} ) { print " - ". $ErrorCode{$Detail}; } print "\n"; } } print "\n\tPercentage(s):\n"; foreach my $Details (sort {$a cmp $b} keys %ServerResponseTotal) { $percentage = (($ServerResponseTotal{$Details} / $ServerResponseOverallTotal) * 100); print "\t\t" . ucfirst($Details) . " - "; printf("%.2f",$percentage); print " %\n"; } } if ( (keys %Warning) ) { print "\nWarnings:\n"; foreach my $Line (sort {$Warning{$b} <=> $Warning{$a}} keys %Warning) { print "\t" . $Line . " - ". $Warning{$Line} . " Time(s)\n"; } } if ( (keys %TripleBounce) ) { $ct = 0; foreach(keys %TripleBounce) { $ct++; } print "\nTripleBounces:\t"; print "$ct times\n"; } if (($#OtherList >= 0) and (not $IgnoreUnmatched)) { print "\n**Unmatched Entries**\n"; print @OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: logwatch-7.14/scripts/services/mailscanner0000644000175000017500000007225115075123620021220 0ustar logwatchlogwatch ######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # and copy: # Mike Tremaine # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ######################################################## # This was written and is maintained by: # Mike Tremaine # # Sophos Support and other improvements by Mark W. Nienberg # MailScan_Spam_Act contributed by Kev Green # # Some more clean up rules based on extensive use of some MailScanner # settings and F-Prot and ClamAV as dual scanners by Hugo van der Kooij # # More F-Prot code from John Wilcock # ######################################################## ##################################################### ## Copyright (c) 2008 Mike Tremaine ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### #use diagnostics; use strict; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $phishing_detail = $ENV{'mailscanner_phishing_detail'} || 0; my $mailscanner_phishingthreshold = $ENV{'mailscanner_phishingthreshold'} || 0; #Init Counters my $MailScan_bytes = my $MailScan_Content = my $MailScan_Delivered = my $MailScan_Other = my $MailScan_RBL = my $MailScan_Received = my $MailScan_Spam = my $MailScan_Unscanned = my $MailScan_Virus = my $SA_timeout = my $MailScan_ScannerTimeout = my $MailScan_GoodWatermark = my $MailScan_BadWatermark = my $MailScan_SkipWatermark = my $SpamAssassin_Rule_Actions = my $MailScan_Deleted_pdb = my $MailScan_Found_pdb = my $MailScan_Spam_Virus = my $MailScan_VirtualHost= my $MailScan_Virus_Antivir= my $MailScan_Virus_BitDefender= my $MailScan_Virus_ClamAv= my $MailScan_Virus_Clamd= my $MailScan_Virus_ClamAVModule= my $MailScan_Virus_Fprot= my $MailScan_Virus_McAfee= my $MailScan_Virus_Kaspersky= my $MailScan_Virus_Sophos= my $MailScan_Virus_SophosSavi= my $MailScan_FilenameAllow= my $MailScan_FilenameBanned= my $MailScan_FiletypeAllow= my $MailScan_FiletypeBanned= my $MailScan_Whitelisted= my $MailScan_Blacklisted= my $MailScan_Phishing= my $MailScan_FormTag= my $MailScan_ScriptTag= my $MailScan_IframeTag= my $MailScan_ObjectTag= my $MailScan_ImgTag= my $MailWatchSQL= my $DisarmedQuarantined= my $SACacheHit= my $MailScan_Skipwatermark= 0; #Initalize hashes my ( %Spam_Virus_Found, %OtherList, %SpamAssassin_Action, %SpamAssassin_Message, %ImgTagSource, %ObjectTagSource, %SpamAssassin_Rule, %IframeTagSource, %FormTagSource, %ScriptTagSource, %PhishingSourceDest, %PhishingSource, %FilenameAllow, %FiletypeAllow, %FilenameType, %FiletypeType, %ContentType, %RBLType, %Blacklisted_Host, %Whitelisted_Host, %Hostlist, %VirusType_Antivir, %VirusType_BitDefender, %VirusType_ClamAv, %VirusType_Clamd, %VirusType_Fprot, %VirusType_ClamAVModule, %VirusType_McAfee, %VirusType_Kaspersky, %VirusType_Sophos, %MailScan_Spam_Act, %VirusScannerTimeout, %VirusType_SophosSavi, ); while (defined(my $ThisLine = )) { #($QueueID) = ($ThisLine =~ m/^([a-zA-Z0-9]+): / ); $ThisLine =~ s/^[a-zA-Z0-9]+: //; if ( ( $ThisLine =~ m/^Saved infected/ ) or ( $ThisLine =~ m/^Expanding TNEF archive/ ) or #TNEF Noise remove for now. IF anyone wants this counted just speak up -mgt ( $ThisLine =~ m/has had TNEF winmail.dat removed/ ) or ( $ThisLine =~ m/added TNEF contents/ ) or ( $ThisLine =~ m/^Warned about/ ) or ( $ThisLine =~ m/^Sender Warnings:/ ) or ( $ThisLine =~ m/X-Spam/ ) or ( $ThisLine =~ m/Using locktype = (flock|posix)/ ) or ( $ThisLine =~ m/Creating hardcoded struct_flock subroutine for / ) or ( $ThisLine =~ m/New Batch: Found/ ) or ( $ThisLine =~ m/Attempting to disinfect/ ) or ( $ThisLine =~ m/Rescan found/ ) or ( $ThisLine =~ m/Virus Re-scanning:/ ) or ( $ThisLine =~ m/Content Checks: Fixed awkward MIME boundary for Cyrus IMAP/ ) or ( $ThisLine =~ m/Delete bayes lockfile/ ) or ( $ThisLine =~ m/MailScanner E-Mail Virus Scanner version/ ) or ( $ThisLine =~ m/MailScanner child dying of old age/ ) or ( $ThisLine =~ m/MailScanner child caught a SIGHUP/ ) or ( $ThisLine =~ m/Virus and Content Scanning/ ) or ( $ThisLine =~ m/Virus Scanning: [\w]+ found/ ) or ( $ThisLine =~ m/Found spam based virus Sanesecurity\./ ) or ( $ThisLine =~ m/Virus Scanning: ClamAV Module found [\d]+ infections/ ) or ( $ThisLine =~ m/^ClamAV virus database has been updated/ ) or ( $ThisLine =~ m/^ClamAV update of/ ) or ( $ThisLine =~ m/^ClamAV scanner using unrar command / ) or ( $ThisLine =~ m/Saved entire message to/ ) or ( $ThisLine =~ m/Spam Checks: Starting/ ) or ( $ThisLine =~ m/SophosSAVI .+ recognizing [0-9]+ viruses/ ) or ( $ThisLine =~ m/SophosSAVI using [0-9]+ IDE files/ ) or ( $ThisLine =~ m/Sophos SAVI library has been updated/ ) or ( $ThisLine =~ m/Sophos.*update.* detected, resetting SAVI/ ) or #( $ThisLine =~ m/RBL checks: .+ found in RFC-IGNORANT-POSTMASTER/ ) or ( $ThisLine =~ m/F-Prot found/ ) or ( $ThisLine =~ m/SpamAssassin Bayes database rebuild starting|preparing|completed/ ) or ( $ThisLine =~ m/Rebuilding SpamAssassin Bayes database/ ) or ( $ThisLine =~ m/Skipping SpamAssassin while waiting for Bayes/ ) or ( $ThisLine =~ m/Enabling SpamAssassin auto-whitelist functionality/ ) or ( $ThisLine =~ m/Bayes database rebuild is due/ ) or ( $ThisLine =~ m/Content Checks: Detected and will convert|disarm HTML/ ) or ( $ThisLine =~ m/Content Checks: Detected and have disarmed HTML message/ ) or ( $ThisLine =~ m/Content Checks: Found [0-9]+ problems/ ) or ( $ThisLine =~ m/Read [0-9]+ hostnames from the phishing whitelist/ ) or ( $ThisLine =~ m/completed at [0-9]+ bytes per second/ ) or ( $ThisLine =~ m/Message .+ from .+ to .+ is/ ) or ( $ThisLine =~ m/^[A-F0-9]+\.[A-F0-9]{5} to/ ) or #for postfix Requeue: ( $ThisLine =~ m/^calling custom .* function/ ) or ( $ThisLine =~ m/^Initialising database connection/ ) or ( $ThisLine =~ m/^Finished initialising database connection/ ) or ( $ThisLine =~ m/^Disconnected from the database/ ) or ( $ThisLine =~ m/^ tag found in message/ ) or ( $ThisLine =~ m/^Viruses marked as silent:/ ) or ( $ThisLine =~ m/^Saved archive copies of/ ) or ( $ThisLine =~ m/^Logging message .+ to SQL/ ) or ( $ThisLine =~ m/^Started SQL Logging child/ ) or ( $ThisLine =~ m/^Starting up SQL Whitelist|Blacklist/ ) or ( $ThisLine =~ m/^Read .+ whitelist|blacklist entries/ ) or ( $ThisLine =~ m/^Closing down by-domain spam whitelist|blacklist/ ) or ( $ThisLine =~ m/^Connected to SpamAssassin cache database/ ) or ( $ThisLine =~ m/^Using SpamAssassin results cache/ ) or ( $ThisLine =~ m/^Expired .+ records from the SpamAssassin cache/ ) or ( $ThisLine =~ m/^Batch (\([0-9]+ messages?\) )?processed in .+ seconds/ ) or ( $ThisLine =~ m/^\"Always Looked Up Last\" took .+ seconds/ ) or ( $ThisLine =~ m/^MailScanner child dying after Bayes rebuild/ ) or ( $ThisLine =~ m/^Files hidden in very deeply nested archive/ ) or #IPBlocking -mgt ( $ThisLine =~ m/^Initialising IP blocking/ ) or ( $ThisLine =~ m/^Closing down IP blocking/ ) or ( $ThisLine =~ m/Whitelist refresh time reached/ ) or ( $ThisLine =~ m/Skipping sender of precedence list/ ) or ( $ThisLine =~ m/^Read \d+ IP blocking entries from/ ) or #This for Kaspersky I guess it is duplicated by Content checks, remove if not -mgt ( $ThisLine =~ m/^\/var\/spool\/MailScanner\/incoming\/.+SUSPICION/ ) or # New processing database ( $ThisLine =~ m/Connected to [Pp]rocessing(?:-messages| Attempts) [Dd]atabase/ ) or ( $ThisLine =~ m/Found 0 messages in the [Pp]rocessing(?:-messages| Attempts) [Dd]atabase/ ) or ( $ThisLine =~ m/Reading configuration file/ ) or ( $ThisLine =~ m/^SpamAssassin temporary working directory is/ ) or ( $ThisLine =~ m/ignored whitelist, had .+ recipients/ ) ) { # We don't care about these } elsif ( $ThisLine =~ m/New Batch: Scanning ([0-9]+) messages, ([0-9]+) bytes/i) { $MailScan_Received = $MailScan_Received + $1; $MailScan_bytes = $MailScan_bytes + $2; } elsif ( $ThisLine =~ m/New Batch: Forwarding ([0-9]+) unscanned messages, ([0-9]+) bytes/i) { $MailScan_Received = $MailScan_Received + $1; $MailScan_Unscanned = $MailScan_Unscanned + $1; $MailScan_bytes = $MailScan_bytes + $2; } elsif ( $ThisLine =~ m/Delivered ([0-9]+)( cleaned)? messages/) { $MailScan_Delivered = $MailScan_Delivered + $1; } elsif ( $ThisLine =~ m/Spam Checks: Found ([0-9]+) spam messages/) { $MailScan_Spam = $MailScan_Spam + $1; } elsif ( $ThisLine =~ m/Virus Scanning: Found ([0-9]+) viruses/) { $MailScan_Virus = $MailScan_Virus + $1; } elsif ( $ThisLine =~ m/Found spam-virus (\S+) in/i) { $MailScan_Spam_Virus++; $Spam_Virus_Found{$1}++; } elsif ( $ThisLine =~ m/infected message .+ came from (.*)/i) { $MailScan_VirtualHost = $MailScan_VirtualHost + 1; $Hostlist{$1}++; } elsif ( $ThisLine =~ m/Other Checks: Found ([0-9]+) problems/) { $MailScan_Other = $MailScan_Other + $1; } elsif ($ThisLine =~ m/Contains signature of the worm (.+)/) { $VirusType_Antivir{$1}++; $MailScan_Virus_Antivir++; } elsif ($ThisLine =~ m/:infected: (.+)/i) { #without the leading : this would match Fprot so error on the side of matching to much -mgt $VirusType_BitDefender{$1}++; $MailScan_Virus_BitDefender++; } elsif ( ($ThisLine =~ m/^\/var\/spool\/MailScanner\/incoming\/.+: ([\w\_\-\.\/]+) FOUND/i) or ($ThisLine =~ m/Clamd::INFECTED:: ?(\S+) ::/i) ) { $VirusType_ClamAv{$1}++; $MailScan_Virus_ClamAv++; } elsif ($ThisLine =~ m/ClamAVModule::INFECTED:: ?(.+)::/) { $VirusType_ClamAVModule{$1}++; $MailScan_Virus_ClamAVModule++; } elsif ($ThisLine =~ m/INFECTED:: ?(.+) (FOUND )?::/) { $VirusType_Clamd{$1}++; $MailScan_Virus_Clamd++; } elsif ($ThisLine =~ m/\/.+ Infection: (.+)/i) { $VirusType_Fprot{$1}++; $MailScan_Virus_Fprot++; } elsif ($ThisLine =~ m/\/.+ is a security risk named (.+)/i) { $VirusType_Fprot{$1}++; $MailScan_Virus_Fprot++; } elsif ($ThisLine =~ m/\/.+ is a dropper for (.+)/i) { $VirusType_Fprot{$1}++; $MailScan_Virus_Fprot++; } elsif ($ThisLine =~ m/\/.+ contains (.+)/i) { $VirusType_Fprot{$1}++; $MailScan_Virus_Fprot++; } elsif ($ThisLine =~ m/\/.+ could be/i) { $MailScan_Virus_Fprot++; } elsif ($ThisLine =~ m/Found the (.+) virus !!!/) { $VirusType_McAfee{$1}++; $MailScan_Virus_McAfee++; } elsif ($ThisLine =~ m/^\/var\/spool\/MailScanner\/incoming\/.+INFECTED\s+([\w\_\-\.\/]+)/i) { $VirusType_Kaspersky{$1}++; $MailScan_Virus_Kaspersky++; } elsif ($ThisLine =~ m/infected:\s+([\w\_\-\.\/]+)\^M/i) { $VirusType_Kaspersky{$1}++; $MailScan_Virus_Kaspersky++; } elsif ($ThisLine =~ m/>>> Virus \'(.+)\' found/) { $VirusType_Sophos{$1}++; $MailScan_Virus_Sophos++; } elsif ($ThisLine =~ m/SophosSAVI::INFECTED:: ?(.+)::/) { $VirusType_SophosSavi{$1}++; $MailScan_Virus_SophosSavi++; } elsif ($ThisLine =~ m/Commercial scanner (.+) timed out!/){ $VirusScannerTimeout{$1}++; $MailScan_ScannerTimeout++; } elsif ($ThisLine =~ m/Content Checks: Detected and have disarmed (.+) in HTML message in [\w]+/i) { $ContentType{$1}++; $MailScan_Content++; } elsif ($ThisLine =~ m/Content Checks: Detected (.+) in [\w]+/i) { $ContentType{$1}++; $MailScan_Content++; } elsif ($ThisLine =~ m/Filename Checks: Allowing (.+)/i) { if ($ThisLine =~ m/Allowing.*msg\-[0-9]*\-[0-9]*\.[txt|dat|html]/) { # we don't care about these, regular messages } else { #filter sendmail or postfix tag and "(no rule matched)" my $temp_fc = $1; $temp_fc =~ s/[a-z0-9]{14}\s//i; $temp_fc =~ s/[a-z0-9]{9,12}\.[a-z0-9]{5}\s//i; $temp_fc =~ s/\(no rule matched\)//i; $FilenameAllow{$temp_fc}++; $MailScan_FilenameAllow++; } } elsif ($ThisLine =~ m/Filename Checks: (.+)/i) { #filter sendmail or postfix tag my $temp_fc = lc($1); $temp_fc =~ s/\([a-z0-9]{14}\s/\(/i; $temp_fc =~ s/\([a-z0-9]{9,12}\.[a-z0-9]{5}\s/\(/i; $temp_fc =~ s/\s{10,}/ -space- /; $FilenameType{$temp_fc}++; $MailScan_FilenameBanned++; } elsif ($ThisLine =~ m/Filetype Checks: Allowing (.+)/i) { if ($ThisLine =~ m/Allowing.*msg\-[0-9]*\-[0-9]*\.[txt|dat|html]/) { # we don't care about these, regular messages } else { #filter sendmail or postfix tag and "(no match found)" my $temp_fc = $1; $temp_fc =~ s/[a-z0-9]{14}\s//i; $temp_fc =~ s/[a-z0-9]{9,12}\.[a-z0-9]{5}\s//i; $temp_fc =~ s/\(no match found\)//i; $FiletypeAllow{$temp_fc}++; $MailScan_FiletypeAllow++; } } elsif ($ThisLine =~ m/Filetype Checks: (.+)/i) { #filter sendmail or postfix tag my $temp_fc = lc($1); $temp_fc =~ s/\([a-z0-9]{14}\s/\(/i; $temp_fc =~ s/\([a-z0-9]{9,12}\.[a-z0-9]{5}\s/\(/i; $temp_fc =~ s/\s{10,}/ -space- /; $FiletypeType{$temp_fc}++; $MailScan_FiletypeBanned++; } elsif ($ThisLine =~ m/(Password\-protected archive \(.+\)) in \w+/i) { $MailScan_Other = $MailScan_Other + 1; $FilenameType{$1}++; $MailScan_FilenameBanned++; } elsif ($ThisLine =~ /Spam Actions: .+ actions are (.*)/) { $MailScan_Spam_Act{$1}++; } elsif ($ThisLine =~ /SpamAssassin timed out and was killed/) { $SA_timeout++; } elsif ( $ThisLine =~ m/Message .+ from (.+ \(.+\)) is whitelisted/ ) { $MailScan_Whitelisted++; $Whitelisted_Host{$1}++; } elsif ( $ThisLine =~ m/Message .+ from (.+ \(.+\)) to .+ is spam \(blacklisted\)/ ) { $MailScan_Blacklisted++; $Blacklisted_Host{$1}++; } elsif ($ThisLine =~ m/^Found phishing fraud from (.+) claiming to be (.+) in (.+)/) { $MailScan_Phishing++; #Detailed phishing output set in mailscanner.conf #With variable mailscanner_phishing_detail = 1 if ($phishing_detail) { $PhishingSourceDest{"$1 claiming to be $2 in $3"}++; } else { $PhishingSourceDest{"$1 claiming to be $2"}++; } my $temp_ph = $1; if ($temp_ph =~ m/^https?:\/\/([^\/\? ]+)/i) { $PhishingSource{$1}++; } else { $PhishingSource{$temp_ph}++; } } elsif ($ThisLine =~ m/^Found ip-based phishing fraud from (.+) in/) { $MailScan_Phishing++; my $temp_ph = $1; if ($temp_ph =~ m/^https?:\/\/([\d\.]+)/i) { $PhishingSource{$1}++; } else { $PhishingSource{$temp_ph}++; } $PhishingSource{$1}++; } elsif ($ThisLine =~ m/^Found definite phishing fraud from (.+) in/) { $MailScan_Phishing++; my $temp_ph = $1; if ($temp_ph =~ m/^https?:\/\/([^\/\? ]+)/i) { $PhishingSource{$1}++; } else { $PhishingSource{$temp_ph}++; } } elsif ($ThisLine =~ m/^HTML-Form tag found in message .+ from (.+)/) { $MailScan_FormTag++; $FormTagSource{$1}++; } elsif ($ThisLine =~ m/^HTML-Script tag found in message .+ from (.+)/) { $MailScan_ScriptTag++; $ScriptTagSource{$1}++; } elsif ($ThisLine =~ m/^HTML-IFrame tag found in message .+ from (.+)/) { $MailScan_IframeTag++; $IframeTagSource{$1}++; } elsif ($ThisLine =~ m/^HTML-Object tag found in message .+ from (.+)/) { $MailScan_ObjectTag++; $ObjectTagSource{$1}++; } elsif ($ThisLine =~ m/^HTML Img tag found in message .+ from (.+)/) { $MailScan_ImgTag++; $ImgTagSource{$1}++; } elsif ($ThisLine =~ m/Logged to MailWatch SQL/) { $MailWatchSQL++; } elsif ($ThisLine =~ m/Quarantining modified message for/) { $DisarmedQuarantined++; } elsif ($ThisLine =~ m/SpamAssassin cache hit for message/) { $SACacheHit++; } elsif ($ThisLine =~ m/RBL checks: .+ found in (.+)/i) { $RBLType{$1}++; $MailScan_RBL++; } elsif ($ThisLine =~ m/Valid Watermark HASH found in Message/) { $MailScan_Skipwatermark++; } elsif ($ThisLine =~ m/Message .+ from .+ has valid watermark/) { $MailScan_GoodWatermark++; } elsif ( ($ThisLine =~ m/Message .+ had bad watermark/) || ($ThisLine =~ m/Message .+ from .+ has no \(or invalid\) watermark or sender address/) ) { $MailScan_BadWatermark++; } elsif ($ThisLine =~ m/SpamAssassin Rule Actions: rule ([^ ]*) caused action ([^ ]*) .*in message ([0-9a-f.]*)/i) { $SpamAssassin_Rule_Actions++; $SpamAssassin_Rule{$1}++; $SpamAssassin_Action{$2}++; $SpamAssassin_Message{$3}++; } elsif ($ThisLine =~ m/Deleted (\d+) messages from processing-database/) { $MailScan_Deleted_pdb += $1; } elsif ($ThisLine =~ m/Found (\d+) messages in the [Pp]rocessing(?:-messages| Attempts) [Dd]atabase/) { $MailScan_Found_pdb += $1; } else { chomp($ThisLine); # Report any unmatched entries... $OtherList{$ThisLine}++; } } if ($MailScan_Received > 0) { print "\nMailScanner Status:"; print "\n\t" . $MailScan_Received . ' messages Scanned by MailScanner'; my $size_total = 1 ; if ($MailScan_bytes < 1024) { $size_total = $MailScan_bytes . ' Total Bytes'; } elsif ($MailScan_bytes < 1048576) { $size_total = sprintf("%.1f", ($MailScan_bytes / 1024)) . ' Total KB'; } else { $size_total = sprintf("%.1f", ($MailScan_bytes / 1048576)) . ' Total MB'; } print "\n\t" . $size_total ; } if ($MailScan_Spam > 0) { print "\n\t" . $MailScan_Spam . ' Spam messages detected by MailScanner'; } if (keys %MailScan_Spam_Act) { foreach my $ThisOne (sort keys %MailScan_Spam_Act) { if ($MailScan_Spam_Act{$ThisOne} > 0) { print "\n\t\t" . $MailScan_Spam_Act{$ThisOne} . ' Spam messages with action(s) ' .$ThisOne ; } } } if ($SACacheHit > 0) { print "\n\t\t" . $SACacheHit . ' hits from MailScanner SpamAssassin cache'; } #if ($MailScan_Spam_Virus > 0) { # print "\n\t" . $MailScan_Spam_Virus . ' Spam messages detected by Virus signatures'; #} if ($MailScan_Unscanned > 0) { print "\n\t" . $MailScan_Unscanned . ' Messages forwarded unscanned by MailScanner'; } if ($MailScan_Virus > 0) { print "\n\t" . $MailScan_Virus . ' Viruses found by MailScanner'; } if ($MailScan_Other > 0) { print "\n\t" . $MailScan_Other . ' Banned attachments found by MailScanner'; } if ($MailScan_Content > 0) { print "\n\t" . $MailScan_Content . ' Content Problems found by MailScanner'; } if ($MailScan_Deleted_pdb > 0) { print "\n\t" . $MailScan_Deleted_pdb . " Messages deleted from processing-database"; } if ($MailScan_Found_pdb > 0) { print "\n\t" . $MailScan_Found_pdb . " Messages found in processing-database"; } if ($MailScan_Delivered > 0) { print "\n\t" . $MailScan_Delivered . " Messages delivered by MailScanner\n"; } if ($MailWatchSQL > 0) { print "\n\t" . $MailWatchSQL . " Messages logged to MailWatch database\n"; } if ($SA_timeout > 0) { print "\n\t" . $SA_timeout . " SpamAssassin timeout(s)\n"; } if (keys %VirusScannerTimeout) { print "\n\t" . $MailScan_ScannerTimeout . " virus scanner timeout(s)\n"; foreach my $ThisOne (sort keys %VirusScannerTimeout) { print "\t " . $ThisOne . ": " . $VirusScannerTimeout{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_Antivir) { print "\nAntivir Virus Report: (Total Seen = $MailScan_Virus_Antivir)\n"; foreach my $ThisOne (sort keys %VirusType_Antivir) { print ' ' . $ThisOne . ': ' . $VirusType_Antivir{$ThisOne} . " Times(s)\n"; } } if (keys %VirusType_BitDefender) { print "\nBitDefender Virus Report: (Total Seen = $MailScan_Virus_BitDefender)\n"; foreach my $ThisOne (sort keys %VirusType_BitDefender) { print ' ' . $ThisOne . ': ' . $VirusType_BitDefender{$ThisOne} . " Times(s)\n"; } } if (keys %VirusType_ClamAv) { print "\nClamAV Virus Report: (Total Seen = $MailScan_Virus_ClamAv)\n"; foreach my $ThisOne (sort keys %VirusType_ClamAv) { print ' ' . $ThisOne . ': ' . $VirusType_ClamAv{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_ClamAVModule) { print "\nClamAVModule Virus Report: (Total Seen = $MailScan_Virus_ClamAVModule)\n"; foreach my $ThisOne (sort keys %VirusType_ClamAVModule) { print ' ' . $ThisOne . ': ' . $VirusType_ClamAVModule{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_Clamd) { print "\nClamd Virus Report: (Total Seen = $MailScan_Virus_Clamd)\n"; foreach my $ThisOne (sort keys %VirusType_Clamd) { print ' ' . $ThisOne . ': ' . $VirusType_Clamd{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_Fprot) { print "\nF-Prot Virus Report: (Total Seen = $MailScan_Virus_Fprot)\n"; foreach my $ThisOne (sort keys %VirusType_Fprot) { print ' ' . $ThisOne . ': ' . $VirusType_Fprot{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_Kaspersky) { print "\nKaspersky Virus Report: (Total Seen = $MailScan_Virus_Kaspersky)\n"; foreach my $ThisOne (sort keys %VirusType_Kaspersky) { print ' ' . $ThisOne . ': ' . $VirusType_Kaspersky{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_McAfee) { print "\nMcAfee Virus Report: (Total Seen = $MailScan_Virus_McAfee)\n"; foreach my $ThisOne (sort keys %VirusType_McAfee) { print ' ' . $ThisOne . ': ' . $VirusType_McAfee{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_Sophos) { print "\nSophos Virus Report: (Total Seen = $MailScan_Virus_Sophos)\n"; foreach my $ThisOne (sort keys %VirusType_Sophos) { print ' ' . $ThisOne . ': ' . $VirusType_Sophos{$ThisOne} . " Time(s)\n"; } } if (keys %VirusType_SophosSavi) { print "\nSophosSavi Virus Report: (Total Seen = $MailScan_Virus_SophosSavi)\n"; foreach my $ThisOne (sort keys %VirusType_SophosSavi) { print ' ' . $ThisOne . ': ' . $VirusType_SophosSavi{$ThisOne} . " Time(s)\n"; } } if (keys %Spam_Virus_Found) { print "\nSpam Virus Report: (Total Seen = $MailScan_Spam_Virus)\n"; foreach my $ThisOne (sort keys %Spam_Virus_Found) { print ' ' . $ThisOne . ': ' . $Spam_Virus_Found{$ThisOne} . " Time(s)\n"; } } if (keys %Hostlist) { print "\nVirus Sender Report: (Total Seen = $MailScan_VirtualHost)\n"; foreach my $ThisOne (sort keys %Hostlist) { print ' ' . $ThisOne . ': ' . $Hostlist{$ThisOne} . " Time(s)\n"; } } if (keys %Whitelisted_Host) { print "\nSpam Whitelisted Host Report: (Total Seen = $MailScan_Whitelisted)\n"; foreach my $ThisOne (sort keys %Whitelisted_Host) { print ' ' . $ThisOne . ': ' . $Whitelisted_Host{$ThisOne} . " Time(s)\n"; } } if (keys %Blacklisted_Host) { print "\nSpam Blacklisted Host Report: (Total Seen = $MailScan_Blacklisted)\n"; foreach my $ThisOne (sort keys %Blacklisted_Host) { print ' ' . $ThisOne . ': ' . $Blacklisted_Host{$ThisOne} . " Time(s)\n"; } } if (keys %RBLType) { print "\nRBL Report: (Total Seen = $MailScan_RBL)\n"; foreach my $ThisOne (sort keys %RBLType) { print ' ' . $ThisOne . ': ' . $RBLType{$ThisOne} . " Time(s)\n"; } } if (keys %ContentType) { print "\nContent Report: (Total Seen = $MailScan_Content)"; if ($DisarmedQuarantined > 0) { print " (Quarantined = $DisarmedQuarantined)"; } print "\n"; foreach my $ThisOne (sort keys %ContentType) { print ' ' . $ThisOne . ': ' . $ContentType{$ThisOne} . " Time(s)\n"; } } if (keys %FilenameAllow) { print "\nAllowed Filename Report: (Total Seen = $MailScan_FilenameAllow)\n"; if ($Detail >= 10) { foreach my $ThisOne (sort keys %FilenameAllow) { print ' ' . $ThisOne . ': ' . $FilenameAllow{$ThisOne} . " Time(s)\n"; } } else { print ' ' . "Details Suppressed at level $Detail. Level 10 required.\n"; } } if (keys %FilenameType) { print "\nBanned Filename Report: (Total Seen = $MailScan_FilenameBanned)\n"; foreach my $ThisOne (sort keys %FilenameType) { print ' ' . $ThisOne . ': ' . $FilenameType{$ThisOne} . " Time(s)\n"; } } if (keys %FiletypeAllow) { print "\nAllowed Filetype Report: (Total Seen = $MailScan_FiletypeAllow)\n"; if ($Detail >= 10) { foreach my $ThisOne (sort keys %FiletypeAllow) { print ' ' . $ThisOne . ': ' . $FiletypeAllow{$ThisOne} . " Time(s)\n"; } } else { print ' ' . "Details Suppressed at level $Detail. Level 10 required.\n"; } } if (keys %FiletypeType) { print "\nBanned Filetype Report: (Total Seen = $MailScan_FiletypeBanned)\n"; foreach my $ThisOne (sort keys %FiletypeType) { print ' ' . $ThisOne . ': ' . $FiletypeType{$ThisOne} . " Time(s)\n"; } } if ( (keys %PhishingSource) && ($mailscanner_phishingthreshold > 0) ) { print "\nPhishing Report: (Total Seen = $MailScan_Phishing)\n"; foreach my $ThisOne (sort keys %PhishingSource) { if ( $PhishingSource{$ThisOne} >= $mailscanner_phishingthreshold ) { print ' ' . $ThisOne . ': ' . $PhishingSource{$ThisOne} . " Time(s)\n"; } }; if ($Detail >= 10) { print "\n Detail:\n"; foreach my $ThisOne (sort keys %PhishingSourceDest) { if ( $PhishingSourceDest{$ThisOne} >= $mailscanner_phishingthreshold ) { print ' ' . $ThisOne . ': ' . $PhishingSourceDest{$ThisOne} . " Time(s)\n"; } } } } if (keys %FormTagSource) { print "\nHTML
tag report: (Total Seen = $MailScan_FormTag)\n"; foreach my $ThisOne (sort keys %FormTagSource) { print ' ' . $ThisOne . ': ' . $FormTagSource{$ThisOne} . " Time(s)\n"; } } if (keys %ScriptTagSource) { print "\nHTML