1##########################################################################
2# INN module for innreport (3.*).
3#
4# $Id: innreport_inn.pm 10442 2020-12-09 21:04:31Z iulius $
5#
6# Sample file tested with INN 2.5, 2.4, 2.3, 2.2, 1.7.2 and 1.5.1.
7#
8# (c) 1997-2001 by Fabien Tassin <fta@sofaraway.org>.
9# See innreport for more information.
10#
11# Version 3.1.0.
12#
13##########################################################################
14
15# TODO: add the map file.
16
17package innreport_inn;
18use strict;
19
20my $MIN = 1E10;
21my $MAX = -1;
22
23my %ctlinnd = ('a', 'addhist',     'D', 'allow',
24	       'b', 'begin',       'c', 'cancel',
25	       'u', 'changegroup', 'd', 'checkfile',
26	       'e', 'drop',        'f', 'flush',
27	       'g', 'flushlogs',   'h', 'go',
28	       'i', 'hangup',      's', 'mode',
29	       'j', 'name',        'k', 'newgroup',
30	       'l', 'param',       'm', 'pause',
31	       'v', 'readers',     'H', 'stathist',
32	       'C', 'reject',      'o', 'reload',
33	       'n', 'renumber',    'z', 'reserve',
34	       'p', 'rmgroup',     'A', 'send',
35	       'q', 'shutdown',    'B', 'signal',
36	       'r', 'throttle',    'w', 'trace',
37	       'x', 'xabort',      'y', 'xexec',
38	       'E', 'logmode',     'F', 'feedinfo',
39	       'S', 'status',      'P', 'perl',
40               'L', 'lowmark',     'Y', 'python',
41               'Z', 'timer');
42
43my %timer_names = (idle     => 'idle',
44                   hishave  => 'history lookup',
45                   hisgrep  => 'history grep',
46                   hiswrite => 'history write',
47                   hissync  => 'history sync',
48                   nntpread => 'nntp read',
49                   artparse => 'article parse',
50                   artclean => 'article cleanup',
51                   artwrite => 'article write',
52                   artcncl  => 'article cancel',
53                   artlog   => 'article logging',
54                   sitesend => 'site send',
55                   overv    => 'overview write',
56                   perl     => 'perl filter',
57                   python   => 'python filter',
58                   datamove => 'data move'
59);
60
61my %innfeed_timer_names = (
62                   'idle'    => 'idle',
63		   'blstats' => 'backlog stats',
64		   'stsfile' => 'status file',
65		   'newart'  => 'article new',
66		   'prepart' => 'article prepare',
67		   'readart' => 'article read',
68		   'read'    => 'data read',
69		   'write'   => 'data write',
70		   'cb'      => 'callbacks',
71);
72
73my %nnrpd_timer_names = (
74                   'idle'    => 'idle',
75                   'newnews' => 'newnews',
76);
77
78our %batcher_articles;
79our %batcher_bytes;
80our %batcher_elapsed;
81our %batcher_offered;
82our %cnfsstat;
83our %cnfsstat_cycles;
84our %cnfsstat_rate;
85our %cnfsstat_samples;
86our %cnfsstat_size;
87our %cnfsstat_time;
88our %cnfsstat_used;
89our %controlchan_doit;
90our %controlchan_ihave_site;
91our %controlchan_new;
92our %controlchan_ok;
93our %controlchan_other;
94our %controlchan_rm;
95our %controlchan_sendme_site;
96our %controlchan_skippgp;
97our %controlchan_who;
98our %inn_badart;
99our %innd_accepted;
100our %innd_accepted_sum;
101our %innd_bad_command;
102our %innd_bad_ihave;
103our %innd_bad_msgid;
104our %innd_bad_newsgroup;
105our %innd_bad_sendme;
106our %innd_blocked;
107our %innd_cache;
108our %innd_changegroup;
109our %innd_connect;
110our %innd_connect_sum;
111our %innd_control;
112our %innd_duplicated_size;
113our %innd_duplicated_size_sum;
114our %innd_filter_perl;
115our %innd_filter_python;
116our %innd_his;
117our %innd_huge;
118our %innd_max_conn;
119our %innd_misc;
120our %innd_misc_stat;
121our %innd_newgroup;
122our %innd_no_colon_space;
123our %innd_no_permission;
124our %innd_offered;
125our %innd_offered_size;
126our %innd_offered_size_sum;
127our %innd_offered_sum;
128our %innd_others;
129our %innd_posted_future;
130our %innd_refused;
131our %innd_refused_sum;
132our %innd_rejected;
133our %innd_rejected_size;
134our %innd_rejected_size_sum;
135our %innd_rejected_sum;
136our %innd_rmgroup;
137our %innd_seconds;
138our %innd_seconds_sum;
139our %innd_stored_size;
140our %innd_stored_size_sum;
141our %innd_strange_strings;
142our %innd_time_max;
143our %innd_time_min;
144our %innd_time_num;
145our %innd_time_time;
146our $innd_time_times;
147our %innd_too_many_connects_per_minute;
148our %inn_duplicate;
149our %innfeed_accepted;
150our %innfeed_accepted_size;
151our %innfeed_connect;
152our %innfeed_missing;
153our %innfeed_offered;
154our %innfeed_refused;
155our %innfeed_rejected;
156our %innfeed_rejected_size;
157our %innfeed_seconds;
158our %innfeed_shrunk;
159our %innfeed_spooled;
160our %innfeed_time_max;
161our %innfeed_time_min;
162our %innfeed_time_num;
163our %innfeed_time_time;
164our $innfeed_time_times;
165our %inn_flow;
166our %inn_flow_labels;
167our %inn_flow_size;
168our $inn_flow_size_total;
169our %inn_flow_time;
170our $inn_flow_total;
171our %inn_linecount;
172our %inn_site_path;
173our %inn_tooold;
174our %inn_unapproved;
175our %inn_unapproved_g;
176our %inn_uw_dist;
177our %inn_uw_dist_s;
178our %inn_uw_ng;
179our %inn_uw_ng_s;
180our %inn_uw_site;
181our %innxmit_accepted;
182our %innxmit_accepted_size;
183our %innxmit_afail_host;
184our %innxmit_badart;
185our %innxmit_cfail_host;
186our %innxmit_crefused;
187our %innxmit_duplicate;
188our %innxmit_expire;
189our %innxmit_hiload;
190our %innxmit_ihfail;
191our %innxmit_linecount;
192our %innxmit_missing;
193our %innxmit_nospace;
194our %innxmit_offered;
195our %innxmit_others;
196our %innxmit_refused;
197our %innxmit_rejected;
198our %innxmit_rejected_size;
199our %innxmit_site;
200our %innxmit_times;
201our %innxmit_tooold;
202our %innxmit_unapproved;
203our %innxmit_unapproved_g;
204our %innxmit_uw_dist;
205our %innxmit_uw_dist_s;
206our %innxmit_uw_ng;
207our %innxmit_uw_ng_s;
208our %innxmit_uw_site;
209our %nnrpd_articles;
210our %nnrpd_auth;
211our %nnrpd_bytes;
212our %nnrpd_connect;
213our %nnrpd_curious;
214our %nnrpd_dom_articles;
215our %nnrpd_dom_bytes;
216our %nnrpd_dom_connect;
217our %nnrpd_dom_groups;
218our %nnrpd_dom_no_permission;
219our %nnrpd_dom_post_error;
220our %nnrpd_dom_post_ok;
221our %nnrpd_dom_post_rej;
222our %nnrpd_dom_reset_peer;
223our %nnrpd_dom_timeout;
224our %nnrpd_dom_times;
225our %nnrpd_dom_unrecognized;
226our %nnrpd_gethostbyaddr;
227our %nnrpd_group;
228our %nnrpd_groups;
229our %nnrpd_hierarchy;
230our %nnrpd_no_permission;
231our %nnrpd_post_error;
232our %nnrpd_post_ok;
233our %nnrpd_post_rej;
234our %nnrpd_reset_peer;
235our %nnrpd_resource_elapsed;
236our %nnrpd_resource_idle;
237our %nnrpd_resource_system;
238our %nnrpd_resource_user;
239our %nnrpd_sys_times;
240our %nnrpd_time_max;
241our %nnrpd_time_min;
242our %nnrpd_time_num;
243our %nnrpd_timeout;
244our %nnrpd_times;
245our %nnrpd_time_time;
246our $nnrpd_time_times;
247our %nnrpd_unrecogn_cmd;
248our %nnrpd_unrecognized;
249our %nnrpd_usr_times;
250our %nntplink_accepted;
251our %nntplink_auth;
252our %nntplink_bpipe;
253our %nntplink_connects;
254our %nntplink_eof;
255our %nntplink_expire;
256our %nntplink_fail;
257our %nntplink_failed;
258our %nntplink_fake_connects;
259our %nntplink_hiload;
260our %nntplink_ihfail;
261our %nntplink_nospace;
262our %nntplink_offered;
263our %nntplink_rejected;
264our %nntplink_selecterr;
265our %nntplink_site;
266our %nntplink_sockerr;
267our %nntplink_times;
268our %nocem_badsigs;
269our %nocem_goodsigs;
270our $nocem_lastid;
271our $nocem_newids;
272our %nocem_newids;
273our $nocem_totalbad;
274our $nocem_totalgood;
275our $nocem_totalids;
276our %nocem_totalids;
277our %rnews_bogus_date;
278our %rnews_bogus_dist;
279our %rnews_bogus_ng;
280our $rnews_duplicate;
281our %rnews_host;
282our $rnews_linecount;
283our %rnews_misc;
284our $rnews_no_colon_space;
285our %rnews_rejected;
286our $rnews_too_old;
287our %rnews_unapproved;
288our $server;
289
290# init innd timer
291foreach (values %timer_names) {
292  $innd_time_min{$_} = $MIN;
293  $innd_time_max{$_} = $MAX;
294  $innd_time_time{$_} = 0;   # to avoid a warning... Perl < 5.004
295  $innd_time_num{$_} = 0;    # ...
296}
297$innd_time_times = 0;        # ...
298
299# init innfeed timer
300foreach (values %innfeed_timer_names) {
301  $innfeed_time_min{$_} = $MIN;
302  $innfeed_time_max{$_} = $MAX;
303  $innfeed_time_time{$_} = 0;   # to avoid a warning... Perl < 5.004
304  $innfeed_time_num{$_} = 0;    # ...
305}
306$innfeed_time_times = 0;        # ...
307
308# init nnrpd timer
309foreach (values %nnrpd_timer_names) {
310  $nnrpd_time_min{$_} = $MIN;
311  $nnrpd_time_max{$_} = $MAX;
312  $nnrpd_time_time{$_} = 0;   # to avoid a warning... Perl < 5.004
313  $nnrpd_time_num{$_} = 0;    # ...
314}
315$nnrpd_time_times = 0;        # ...
316
317# collect: Used to collect the data.
318sub collect($$$$$$) {
319  my ($day, $hour, $prog, $res, $left, $CASE_SENSITIVE) = @_;
320
321  return 1 if $left =~ /Reading config from (\S+)$/o;
322
323  ########
324  ## inn (from the "news" log file - not from "news.notice")
325  ##
326  if ($prog eq "inn") {
327    # accepted article
328    if ($res =~ m/[\+j]/o) {
329      $hour =~ s/:.*$//o;
330      $inn_flow{"$day $hour"}++;
331      $inn_flow_total++;
332
333      # Memorize the size. This can only be done with INN >= 1.5xx and
334      # DO_LOG_SIZE = DO.
335
336      # server <msg-id> size [feeds]
337      # or
338      # server <msg-id> (filename) size [feeds]
339
340      my ($s) = $left =~ /^\S+ \S+ (?:\(\S+\) )?(\d+)(?: |$)/o;
341      if ($s) {
342	$inn_flow_size{"$day $hour"} += $s;
343	$inn_flow_size_total += $s;
344      }
345      return 1;
346    }
347
348    # 437 Duplicate article
349    if ($left =~ /(\S+) <[^>]+> (?:437|439) Duplicate(?: article)?$/o) {
350      my $server = $1;
351      $server = lc $server unless $CASE_SENSITIVE;
352      $inn_badart{$server}++;
353      $inn_duplicate{$server}++;
354      return 1;
355    }
356    # 437 Unapproved for
357    if ($left =~ /(\S+) <[^>]+> (?:437|439) Unapproved for \"([^\"]+)\"$/o) {
358      my ($server, $group) = ($1, $2);
359      $server = lc $server unless $CASE_SENSITIVE;
360      $inn_badart{$server}++;
361      $inn_unapproved{$server}++;
362      $inn_unapproved_g{$group}++;
363      return 1;
364    }
365    # 437 Too old -- ...
366    if ($left =~ /(\S+) <[^>]+> (?:437|439) Too old -- /o) {
367      my $server = $1;
368      $server = lc $server unless $CASE_SENSITIVE;
369      $inn_badart{$server}++;
370      $inn_tooold{$server}++;
371      return 1;
372    }
373    # 437 Unwanted site ... in path
374    if ($left =~ /(\S+) <[^>]+> (?:437|439) Unwanted site (\S+) in path$/o) {
375      my ($server, $site) = ($1, $2);
376      $server = lc $server unless $CASE_SENSITIVE;
377      $inn_badart{$server}++;
378      $inn_uw_site{$server}++;
379      $inn_site_path{$site}++;
380      return 1;
381    }
382    # 437 Unwanted newsgroup "..."
383    if ($left =~ /(\S+) <[^>]+> (?:437|439) Unwanted newsgroup \"(\S+)\"$/o) {
384      my ($server, $group) = ($1, $2);
385      ($group) = split(/,/, $group);
386      $server = lc $server unless $CASE_SENSITIVE;
387      $inn_badart{$server}++;
388      $inn_uw_ng_s{$server}++;
389      $inn_uw_ng{$group}++;
390      return 1;
391    }
392    # 437 Unwanted distribution "..."
393    if ($left =~ /(\S+) <[^>]+> (?:437|439) Unwanted distribution \"(\S+)\"$/o) {
394      my ($server, $dist) = ($1, $2);
395      $server = lc $server unless $CASE_SENSITIVE;
396      $inn_badart{$server}++;
397      $inn_uw_dist_s{$server}++;
398      $inn_uw_dist{$dist}++;
399      return 1;
400    }
401    # 437 Linecount x != y +- z
402    if ($left =~ /(\S+) <[^>]+> (?:437|439) Linecount/o) {
403      my $server = $1;
404      $server = lc $server unless $CASE_SENSITIVE;
405      $inn_badart{$server}++;
406      $inn_linecount{$server}++;
407      return 1;
408    }
409    # 437 No colon-space in "xxxx" header
410    if ($left =~ /(\S+) <[^>]+> (?:437|439) No colon-space in \"[^\"]+\" header/o) {
411      my $server = $1;
412      $server = lc $server unless $CASE_SENSITIVE;
413      $inn_badart{$server}++;
414      $innd_others{$server}++;
415      $innd_no_colon_space{$server}++;
416      return 1;
417    }
418    # 437 Article injected or posted in the future -- "xxxxx"
419    if ($left =~ /(\S+) <[^>]+> (?:437|439) Article injected or posted in the future -- \"[^\"]+\"/o) {
420      my $server = $1;
421      $server = lc $server unless $CASE_SENSITIVE;
422      $innd_posted_future{$server}++;
423      $innd_others{$server}++;
424      $inn_badart{$server}++;
425      return 1;
426    }
427    # Article accepted but includes "....."
428    if ($left =~ /(\S+) <[^>]+> Article accepted but includes/o) {
429      my $server = $1;
430      $server = lc $server unless $CASE_SENSITIVE;
431      $innd_strange_strings{$server}++;
432      $innd_others{$server}++;
433      $inn_badart{$server}++;
434      return 1;
435    }
436    # Cancelling <...>
437    if ($left =~ /(\S+) <[^>]+> Cancelling/o) {
438      return 1;
439    }
440    # all others are just counted as "Other"
441    if ($left =~ /(\S+) /o) {
442      my $server = $1;
443      $server = lc $server unless $CASE_SENSITIVE;
444      $inn_badart{$server}++;
445      $innd_others{$server}++;
446      return 1;
447    }
448  }
449
450  ########
451  ## innd
452  if ($prog eq "innd") {
453    ## Note for innd logs:
454    ## there's a lot of entries detected but still not used
455    ## (because of a lack of interest).
456
457    # think it's a dotquad
458    return 1 if $left =~ /^think it\'s a dotquad$/o;
459    if ($left =~ /^SERVER /o) {
460      # SERVER perl filtering enabled
461      return 1 if $left =~ /^SERVER perl filtering enabled$/o;
462      # SERVER perl filtering disabled
463      return 1 if $left =~ /^SERVER perl filtering disabled$/o;
464      # SERVER Python filtering enabled
465      return 1 if $left =~ /^SERVER Python filtering enabled$/o;
466      # SERVER Python filtering disabled
467      return 1 if $left =~ /^SERVER Python filtering disabled$/o;
468      # SERVER cancelled +id
469      return 1 if $left =~ /^SERVER cancelled /o;
470    }
471    # Python filter
472    return 1 if $left =~ /^defined python methods$/o;
473    return 1 if $left =~ /^reloading pyfilter$/o;
474    return 1 if $left =~ /^reloaded pyfilter OK$/o;
475    return 1 if $left =~ /^python interpreter initialized OK$/o;
476    return 1 if $left =~ /^python is not initialized$/o;
477    return 1 if $left =~ /^pyfilter .+ not installed$/o;
478    return 1 if $left =~ /^python method \w+ not found$/o;
479    return 1 if $left =~ /^python: First load, so I can do initialization stuff\.$/o;
480    return 1 if $left =~ /^python: filter_before_reload executing\.\.\.$/o;
481    return 1 if $left =~ /^python: I\'m just reloading, so skip the formalities\.$/o;
482    return 1 if $left =~ /^python: spamfilter successfully hooked into INN$/o;
483    return 1 if $left =~ /^python: state change from \w+ to \w+ - /o;
484    return 1 if $left =~ /^python: filter_close running, bye!$/o;
485    # rejecting[perl]
486    if ($left =~ /^rejecting\[perl\] <[^>]+> \d+ (.*)/o) {
487      $innd_filter_perl{$1}++;
488      return 1;
489    }
490    # rejecting[python]
491    if ($left =~ /^rejecting\[python\] <[^>]+> \d+ (.*)/o) {
492      $innd_filter_python{$1}++;
493      return 1;
494    }
495    # closed lost
496    return 1 if $left =~ /^\S+ closed lost \d+/o;
497    # new control command
498    if ($left =~ /^ctlinnd command (\w)(:.*)?/o) {
499      my $command = $1;
500      my $cmd = $ctlinnd{$command};
501      $cmd = $command unless $cmd;
502      return 1 if $cmd eq 'flush'; # to avoid a double count
503      $innd_control{"$cmd"}++;
504      return 1;
505    }
506    # old control command (by letter)
507    if ($left =~ /^(\w)$/o) {
508      my $command = $1;
509      my $cmd = $ctlinnd{$command};
510      $cmd = $command unless $cmd;
511      return 1 if $cmd eq 'flush'; # to avoid a double count
512      $innd_control{"$cmd"}++;
513      return 1;
514    }
515    # old control command (letter + reason)
516    if ($left =~ /^(\w):.*$/o) {
517      my $command = $1;
518      my $cmd = $ctlinnd{$command};
519      $cmd = $command unless $cmd;
520      return 1 if $cmd eq 'flush'; # to avoid a double count
521      $innd_control{"$cmd"}++;
522      return 1;
523    }
524    # opened
525    return 1 if $left =~ /\S+ opened \S+:\d+:file$/o;
526    # buffered
527    return 1 if $left =~ /\S+ buffered$/o;
528    # spawned
529    return 1 if $left =~ /\S+ spawned \S+:\d+:proc:\d+$/o;
530    return 1 if $left =~ /\S+ spawned \S+:\d+:file$/o;
531    # running
532    return 1 if $left =~ /\S+ running$/o;
533    # sleeping
534    if ($left =~ /(\S+):\d+:proc:\d+ sleeping$/o) {
535      my $server = $1;
536      $server = lc $server unless $CASE_SENSITIVE;
537      $innd_blocked{$server}++;
538      return 1;
539    }
540    # blocked sleeping
541    if ($left =~ /(\S+):\d+:proc:\d+ blocked sleeping/o) {
542      my $server = $1;
543      $server = lc $server unless $CASE_SENSITIVE;
544      $innd_blocked{$server}++;
545      return 1;
546    }
547    if ($left =~ /(\S+):\d+ blocked sleeping/o) {
548      my $server = $1;
549      $server = lc $server unless $CASE_SENSITIVE;
550      $innd_blocked{$server}++;
551      return 1;
552    }
553    # restarted
554    return 1 if $left =~ m/^\S+ restarted$/o;
555    # starting
556    return 1 if $left =~ m/^\S+ starting$/o;
557    # readclose
558    return 1 if $left =~ m/^\S+:\d+ readclose+$/o;
559    # rejected 502
560    if ($left =~ m/^(\S+) rejected 502$/) {
561      my $server = $1;
562      $server = lc $server unless $CASE_SENSITIVE;
563      $innd_no_permission{$server}++;
564      return 1;
565    }
566    # rejected 505
567    if ($left =~ m/^(\S+) rejected 505$/) {
568      my $server = $1;
569      $server = lc $server unless $CASE_SENSITIVE;
570      $innd_too_many_connects_per_minute{$server}++;
571      return 1;
572    }
573    # connected
574    #
575    # Record <server>:<channel> instead of just <server> as otherwise we may
576    # miss some persistent connection newsfeeds that in any given innreport
577    # reporting period may not record any connect entries.  We'll accumulate
578    # these into totals at the end of processing.
579    if ($left =~ /^(\S+) connected (\d+)/o) {
580      my $server = "$1:$2";
581      $server = lc $server unless $CASE_SENSITIVE;
582      $innd_connect{$server}++;
583      return 1;
584    }
585
586    # logstatus (information written in inn_status.html)
587    return 1 if ($left =~ /\S+ status seconds \d+ accepted \d+ refused \d+ rejected \d+ duplicate \d+ accepted size \d+ duplicate size \d+(?: rejected size \d+)?$/o);
588
589    # closed/checkpoint (with times)
590    #
591    # Add all checkpoints.  They contain stats generated since the last
592    # checkpoint.
593    # On a closed, a checkpoint is generated by innd.  It is therefore
594    # useless to take into account a line corresponding to a closed status.
595    if ($left =~ /(\S+:\d+) (checkpoint|closed) seconds (\d+) accepted (\d+) refused (\d+) rejected (\d+) duplicate (\d+) accepted size (\d+) duplicate size (\d+)(?: rejected size (\d+))?$/o) {
596      my ($server, $status, $seconds, $accepted, $refused, $rejected, $duplicate, $accptsize, $dupsize, $rjctsize) =
597	($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
598      $server = lc $server unless $CASE_SENSITIVE;
599
600      if ($status eq 'checkpoint') {
601        $innd_seconds{$server}         += $seconds;
602        $innd_accepted{$server}        += $accepted;
603        $innd_refused{$server}         += $refused;
604        $innd_rejected{$server}        += $rejected;
605        $innd_stored_size{$server}     += $accptsize;
606        $innd_duplicated_size{$server} += $dupsize;
607        $innd_rejected_size{$server}   += ($rjctsize || 0);
608      }
609      return 1;
610    }
611    # closed (without times (?))
612    return 1 if $left =~ m/\S+ closed$/o;
613    # closed (for a cancel feed - MODE CANCEL)
614    return 1 if $left =~ m/localhost:\d+ closed seconds \d+ cancels \d+$/o;
615    # flush
616    if ($left =~ /(\S+) flush$/o) {
617      $innd_control{"flush"}++;
618      return 1;
619    }
620    # flush-file
621    if ($left =~ /flush_file/) {
622       $innd_control{"flush_file"}++;
623       return 1;
624     }
625    # too many connections from site
626    if ($left =~ /too many connections from (\S+)/o) {
627      $innd_max_conn{$1}++;
628      return 1;
629    }
630    # overview exit 0 elapsed 23 pid 28461
631    return 1 if $left =~ m/\S+ exit \d+ .*$/o;
632    # internal rejecting huge article
633    if ($left =~ /(\S+) internal rejecting huge article/o) {
634      my $server = $1;
635      $server =~ s/:\d+$//o;
636      $server = lc $server unless $CASE_SENSITIVE;
637      $innd_huge{$server}++;
638      return 1;
639    }
640    # internal closing free channel
641    if ($left =~ /(\S+) internal closing free channel/o) {
642      $innd_misc{"Free channel"}++;
643      return 1;
644    }
645    # internal (other)
646    return 1 if $left =~ /\S+ internal/o;
647    # wakeup
648    return 1 if $left =~ /\S+ wakeup$/o;
649    # throttle
650    if ($left =~ /(\S+) throttled? /) {
651      $innd_control{"throttle"}++;
652      return 1;
653    }
654    # profile timer
655    # ME time X nnnn X(X) [...]
656    # The exact timers change from various versions of INN, so try to deal
657    # with this in a general fashion.
658    if ($left =~ m/^\S+\s+                         # ME
659                   time\s(\d+)\s+                  # time
660                   ((?:\S+\s\d+\(\d+\)\s*)+)       # timer values
661                   $/ox) {
662      $innd_time_times += $1;
663      my $timers = $2;
664
665      while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
666        my $name = $timer_names{$1} || $1;
667        $innd_time_time{$name} += $2;
668        if ($3) {
669	  my $average = $2 / $3;
670	  $innd_time_num{$name} += $3;
671          my $min = $innd_time_min{$name};
672          $innd_time_min{$name} = $average
673            if (!defined($min) || $min > $average);
674          my $max = $innd_time_max{$name};
675          $innd_time_max{$name} = $average
676            if (!defined($max) || $max < $average);
677        }
678      }
679      return 1;
680    }
681    # ME time xx idle xx(xx)     [ bug ? a part of timer ?]
682    return 1 if $left =~ m/^ME time \d+ idle \d+\(\d+\)\s*$/o;
683    # ME HISstats x hitpos x hitneg x missed x dne
684    #
685    # from innd/his.c:
686    # HIShitpos: the entry existed in the cache and in history.
687    # HIShitneg: the entry existed in the cache but not in history.
688    # HISmisses: the entry was not in the cache, but was in the history file.
689    # HISdne:    the entry was not in cache or history.
690    if ($left =~ m/^ME\ HISstats                  # ME HISstats
691	           \ (\d+)\s+hitpos               # hitpos
692	           \ (\d+)\s+hitneg               # hitneg
693	           \ (\d+)\s+missed               # missed
694                   \ (\d+)\s+dne                  # dne
695	           $/ox) {
696      $innd_his{'Positive hits'} += $1;
697      $innd_his{'Negative hits'} += $2;
698      $innd_his{'Cache misses'}  += $3;
699      $innd_his{'Do not exist'}  += $4;
700      return 1;
701    }
702    # SERVER history cache final: 388656 lookups, 1360 hits
703    if ($left =~ m/^SERVER history cache final: (\d+) lookups, (\d+) hits$/) {
704      $innd_cache{'Lookups'} += $1;
705      $innd_cache{'Hits'}    += $2;
706      return 1;
707    }
708    # bad_hosts (appears after a "cant gesthostbyname" from a feed)
709    return 1 if $left =~ m/\S+ bad_hosts /o;
710    # cant read
711    return 1 if $left =~ m/\S+ cant read/o;
712    # cant write
713    return 1 if $left =~ m/\S+ cant write/o;
714    # cant flush
715    return 1 if $left =~ m/\S+ cant flush/o;
716    # spoolwake
717    return 1 if $left =~ m/\S+ spoolwake$/o;
718    # spooling
719    return 1 if $left =~ m/\S+ spooling/o;
720    # DEBUG
721    return 1 if $left =~ m/^DEBUG /o;
722    # NCmode
723    return 1 if $left =~ m/\S+ NCmode /o;
724    # outgoing
725    return 1 if $left =~ m/\S+ outgoing/o;
726    # inactive
727    return 1 if $left =~ m/\S+ inactive/o;
728    # timeout
729    return 1 if $left =~ m/\S+ timeout/o;
730    # lcsetup
731    return 1 if $left =~ m/\S+ lcsetup/o;
732    # rcsetup
733    return 1 if $left =~ m/\S+ rcsetup/o;
734    # flush_all
735    return 1 if $left =~ m/\S+ flush_all/o;
736    # buffered
737    return 1 if $left =~ m/\S+ buffered$/o;
738    # descriptors
739    return 1 if $left =~ m/\S+ descriptors/o;
740    # ccsetup
741    return 1 if $left =~ m/\S+ ccsetup/o;
742    # renumbering
743    return 1 if $left =~ m/\S+ renumbering/o;
744    # renumber
745    return 1 if $left =~ m/\S+ renumber /o;
746    # ihave from me
747    if ($left =~ m/\S+ ihave_from_me /o) {
748      $controlchan_ihave_site{'ME'}++;
749      return 1;
750    }
751    # sendme from me
752    if ($left =~ m/\S+ sendme_from_me /o) {
753      $controlchan_sendme_site{'ME'}++;
754      return 1;
755    }
756    # newgroup
757    if ($left =~ m/\S+ newgroup (\S+) as (\S)/o) {
758      $innd_newgroup{$1} = $2;
759      return 1;
760    }
761    # rmgroup
762    if ($left =~ m/\S+ rmgroup (\S+)$/o) {
763      $innd_rmgroup{$1}++;
764      return 1;
765    }
766    # changegroup
767    if ($left =~ m/\S+ change_group (\S+) to (\S)/o) {
768      $innd_changegroup{$1} = $2;
769      return 1;
770    }
771    # paused
772    if ($left =~ m/(\S+) paused /o) {
773      $innd_control{"paused"}++;
774      return 1;
775    }
776    # throttled
777    return 1 if $left =~ m/\S+ throttled/o;
778    # reload
779    if ($left =~ m/(\S+) reload/o) {
780      $innd_control{"reload"}++;
781      return 1;
782    }
783    # shutdown
784    if ($left =~ m/(\S+) shutdown/o) {
785      $innd_control{"shutdown"}++;
786      return 1;
787    }
788    # re-executed
789    return 1 if $left =~ m/^SERVER execv /o;
790    # SERVER servermode paused
791    return 1 if ($left =~ /(\S+) servermode paused$/o);
792    # SERVER servermode running
793    return 1 if ($left =~ /(\S+) servermode running$/o);
794    # SERVER flushlogs paused
795    if ($left =~ /(\S+) flushlogs /) {
796      $innd_control{"flushlogs"}++;
797      return 1;
798    }
799    # think it's a dotquad
800    return 1 if $left =~ /think it\'s a dotquad: /o;
801    # bad_ihave
802    if ($left =~ /(\S+) bad_ihave /) {
803      my $server = $1;
804      $server =~ s/:\d+$//o;
805      $server = lc $server unless $CASE_SENSITIVE;
806      $innd_bad_ihave{$server}++;
807      return 1;
808    }
809    # bad_messageid
810    if ($left =~ /(\S+) bad_messageid/o) {
811      my $server = $1;
812      $server =~ s/:\d+$//o;
813      $server = lc $server unless $CASE_SENSITIVE;
814      $innd_bad_msgid{$server}++;
815      return 1;
816    }
817    # bad_sendme
818    if ($left =~ /(\S+) bad_sendme /o) {
819      my $server = $1;
820      $server =~ s/:\d+$//o;
821      $server = lc $server unless $CASE_SENSITIVE;
822      $innd_bad_sendme{$server}++;
823      return 1;
824    }
825    # bad_command
826    if ($left =~ /(\S+) bad_command /o) {
827      my $server = $1;
828      $server =~ s/:\d+$//o;
829      $server = lc $server unless $CASE_SENSITIVE;
830      $innd_bad_command{$server}++;
831      return 1;
832    }
833    # bad_newsgroup
834    if ($left =~ /(\S+) bad_newsgroup /o) {
835      my $server = $1;
836      $server =~ s/:\d+$//o;
837      $innd_bad_newsgroup{$server}++;
838      $server = lc $server unless $CASE_SENSITIVE;
839      return 1;
840    }
841    if ($left =~ m/ cant /o) {
842      # cant select Bad file number
843      if ($left =~ / cant select Bad file number/o) {
844	$innd_misc{"Bad file number"}++;
845	return 1;
846      }
847      # cant gethostbyname
848      if ($left =~ / cant gethostbyname/o) {
849	$innd_misc{"gethostbyname error"}++;
850	return 1;
851      }
852      # cant accept RCreader
853      if ($left =~ / cant accept RCreader /o) {
854	$innd_misc{"RCreader"}++;
855	return 1;
856      }
857      # cant sendto CCreader
858      if ($left =~ / cant sendto CCreader /o) {
859	$innd_misc{"CCreader"}++;
860	return 1;
861      }
862      # cant (other) skipped - not particularly interesting
863      return 1;
864    }
865    # bad_newsfeeds no feeding sites
866    return 1 if $left =~ /\S+ bad_newsfeeds no feeding sites/o;
867    # CNFS: cycbuff rollover - possibly interesting
868    return 1 if $left =~ /CNFS(?:-sm)?: cycbuff \S+ rollover to cycle/o;
869    # CNFS: CNFSflushallheads: flushing - possibly interesting
870    return 1 if $left =~ /CNFS(?:-sm)?: CNFSflushallheads: flushing /o;
871    # CNFS: metacycbuff rollover with SEQUENTIAL
872    return 1 if $left =~ /CNFS(?:-sm)?: metacycbuff \S+ cycbuff is moved to /o;
873    # Cleanfeed status reports
874    return 1 if $left =~ /^filter: status/o;
875    return 1 if $left =~ /^filter: Reloading bad files/o;
876    return 1 if $left =~ /^filter: Saved EMP database/o;
877    return 1 if $left =~ /^filter: Restored EMP database/o;
878  }
879  ########
880  ## innfeed
881  if ($prog eq "innfeed") {
882    # connected
883    if ($left =~ /(\S+):\d+ connected$/) {
884      my $server = $1;
885      $server = lc $server unless $CASE_SENSITIVE;
886      $innfeed_connect{$server}++;
887      return 1;
888    }
889    # closed periodic
890    return 1 if $left =~ m/\S+:\d+ closed periodic$/o;
891    # periodic close
892    return 1 if $left =~ m/\S+:\d+ periodic close$/o;
893    # checkpoint (child)
894    return 1 if $left =~ m/\S+:\d+ checkpoint seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+/o;
895    # final (child)
896    return 1 if $left =~ m/\S+:\d+ final seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+/o;
897    # global (real)
898    return 1 if $left =~ m/\S+ global seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+ missing \d+/o;
899    # checkpoint (real) (new format)
900    if ($left =~ /(\S+) checkpoint seconds (\d+) offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) accsize (\d+) rejsize (\d+) spooled (\d+)/o) {
901      my ($server, $seconds, $offered, $accepted, $refused, $rejected,
902	  $missing, $accepted_size, $rejected_size, $spooled) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
903      $server = lc $server unless $CASE_SENSITIVE;
904      $innfeed_seconds{$server} += $seconds;
905      $innfeed_offered{$server} += $offered;
906      $innfeed_accepted{$server} += $accepted;
907      $innfeed_refused{$server} += $refused;
908      $innfeed_rejected{$server} += $rejected;
909      $innfeed_missing{$server} += $missing;
910      $innfeed_spooled{$server} += $spooled;
911      $innfeed_accepted_size{$server} += $accepted_size;
912      $innfeed_rejected_size{$server} += $rejected_size;
913      return 1;
914    } elsif ($left =~ /(\S+) checkpoint seconds (\d+) offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) spooled (\d+)/o) {
915      my ($server, $seconds, $offered, $accepted, $refused, $rejected,
916	  $missing, $spooled) = ($1, $2, $3, $4, $5, $6, $7, $8);
917      $server = lc $server unless $CASE_SENSITIVE;
918      $innfeed_seconds{$server} += $seconds;
919      $innfeed_offered{$server} += $offered;
920      $innfeed_accepted{$server} += $accepted;
921      $innfeed_refused{$server} += $refused;
922      $innfeed_rejected{$server} += $rejected;
923      $innfeed_missing{$server} += $missing;
924      $innfeed_spooled{$server} += $spooled;
925      return 1;
926    }
927    # checkpoint (only seconds & spooled)
928    if ($left =~ /(\S+) checkpoint seconds (\d+) spooled (\d+)/o) {
929      my ($server, $seconds, $spooled) = ($1, $2, $3);
930      $server = lc $server unless $CASE_SENSITIVE;
931      # Initialize a value for that key (otherwise, it does not appear in the
932      # report, and the total line is wrong).
933      $innfeed_offered{$server} += 0;
934      $innfeed_seconds{$server} += $seconds;
935      $innfeed_spooled{$server} += $spooled;
936      return 1;
937    }
938
939    # final
940    return 1 if $left =~ m/\S+ final seconds/o;
941
942    # ME file xxxx shrunk from yyyy to zzz
943    if ($left =~ /^ME file (.*)\.output shrunk from (\d+) to (\d+)$/) {
944      my ($file, $s1, $s2) = ($1, $2, $3);
945      $file =~ s|^.*/([^/]+)$|$1|; # keep only the server name
946      $innfeed_shrunk{$file} += $s1 - $s2;
947      return 1;
948    }
949    # profile timer
950    # ME time X nnnn X(X) [...]
951    return 1 if $left =~ m/backlogstats/;
952    if ($left =~ m/^\S+\s+                         # ME
953                   time\s(\d+)\s+                  # time
954                   ((?:\S+\s\d+\(\d+\)\s*)+)       # timer values
955                   $/ox) {
956      $innfeed_time_times += $1;
957      my $timers = $2;
958
959      while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
960        my $name = $innfeed_timer_names{$1} || $1;
961        $innfeed_time_time{$name} += $2;
962	if ($3) {
963	  $innfeed_time_num{$name} += $3;
964	  my $average = $2 / $3;
965	  my $min = $innfeed_time_min{$name};
966	  $innfeed_time_min{$name} = $average
967	    if (!defined($min) || $min > $average);
968	  my $max = $innfeed_time_max{$name};
969	  $innfeed_time_max{$name} = $average
970	    if (!defined($max) || $max < $average);
971        }
972      }
973      return 1;
974    }
975    # xxx grabbing external tape file
976    return 1 if $left =~ m/ grabbing external tape file/o;
977    # hostChkCxns - maxConnections was
978    return 1 if $left =~ m/hostChkCxns - maxConnections was /o;
979    # cxnsleep
980    return 1 if $left =~ m/\S+ cxnsleep .*$/o;
981    # idle
982    return 1 if $left =~ m/\S+ idle tearing down connection$/o;
983    # remote
984    return 1 if $left =~ m/\S+ remote .*$/o;
985    # spooling
986    return 1 if $left =~ m/\S+ spooling no active connections$/o;
987    # ME articles total
988    return 1 if $left =~ m/(?:SERVER|ME) articles total \d+ bytes \d+/o;
989    # ME articles active
990    return 1 if $left =~ m/(?:SERVER|ME) articles active \d+ bytes \d+/o;
991    # connect : Connection refused
992    return 1 if $left =~ m/connect : Connection refused/o;
993    # connect : Network is unreachable
994    return 1 if $left =~ m/connect : Network is unreachable/o;
995    # connect : Address family not supported by protocol
996    return 1 if $left =~ m/connect : Address family not supported by protocol/o;
997    # connect : No route to host
998    return 1 if $left =~ m/connect : No route to host/o;
999    # connection vanishing
1000    return 1 if $left =~ m/connection vanishing/o;
1001    # can't resolve hostname
1002    return 1 if $left =~ m/can\'t resolve hostname/o;
1003    # new hand-prepared backlog file
1004    return 1 if $left =~ m/new hand-prepared backlog file/o;
1005    # flush re-connect failed
1006    return 1 if $left =~ m/flush re-connect failed/o;
1007    # internal QUIT while write pending
1008    return 1 if $left =~ m/internal QUIT while write pending/o;
1009    # ME source lost . Exiting
1010    return 1 if $left =~ m/(?:SERVER|ME) source lost . Exiting/o;
1011    # ME starting innfeed (+version & date)
1012    return 1 if $left =~ m/(?:SERVER|ME) starting (?:innfeed|at)/o;
1013    # ME finishing at (date)
1014    return 1 if $left =~ m/(?:SERVER|ME) finishing at /o;
1015    # mode no-CHECK entered
1016    return 1 if $left =~ m/mode no-CHECK entered/o;
1017    # mode no-CHECK exited
1018    return 1 if $left =~ m/mode no-CHECK exited/o;
1019    # closed
1020    return 1 if $left =~ m/^(\S+) closed$/o;
1021    # global (+ seconds offered accepted refused rejected missing)
1022    return 1 if $left =~ m/^(\S+) global/o;
1023    # idle connection still has articles
1024    return 1 if $left =~ m/^(\S+) idle connection still has articles$/o;
1025    # missing article for IHAVE-body
1026    return 1 if $left =~ m/^(\S+) missing article for IHAVE-body$/o;
1027    # cannot continue
1028    return 1 if $left =~ m/^cannot continue/o;
1029    if ($left =~ /^(?:SERVER|ME)/o) {
1030      # ME dropping articles into ...
1031      return 1 if $left =~ / dropping articles into /o;
1032      # ME dropped ...
1033      return 1 if $left =~ / dropped /o;
1034      # ME internal bad data in checkpoint file
1035      return 1 if $left =~ m/ internal bad data in checkpoint/o;
1036      # ME two filenames for same article
1037      return 1 if $left =~ m/ two filenames for same article/o;
1038      # ME unconfigured peer
1039      return 1 if $left =~ m/ unconfigured peer/o;
1040      # exceeding maximum article size
1041      return 1 if $left =~ m/ exceeding maximum article byte/o;
1042      # no space left on device errors
1043      return 1 if $left =~ m/ ioerr fclose/o;
1044      return 1 if $left =~ m/ lock failed for host/o;
1045      return 1 if $left =~ m/ lock file pid-write/o;
1046      return 1 if $left =~ m/ locked cannot setup peer/o;
1047      return 1 if $left =~ m/ received shutdown signal/o;
1048      # unconfigured peer
1049      return 1 if $left =~ m/ unconfigured peer/o;
1050      # ME lock
1051      return 1 if $left =~ m/ lock/o;
1052      # ME exception: getsockopt (0): Socket operation on non-socket
1053      return 1 if $left =~ m/ exception: getsockopt /o;
1054      # ME config aborting fopen (...) Permission denied
1055      return 1 if $left =~ m/ config aborting fopen /o;
1056      # ME cant chmod innfeed.pid....
1057      return 1 if $left =~ m/ cant chmod \S+\/innfeed.pid/o;
1058      return 1 if $left =~ m/ tape open failed /o;
1059      return 1 if $left =~ m/ oserr open checkpoint file:/o;
1060      # ME finishing (quickly)
1061      return 1 if $left =~ m/\(quickly\) /o;
1062      # ME config: value of streaming is not a boolean
1063      return 1 if $left =~ m/config: value of \S+ is not/o;
1064      # innfeed rolling funnel file
1065      return 1 if $left =~ m/ preparing to roll /o;
1066      return 1 if $left =~ m/ reached EOF in /o;
1067      return 1 if $left =~ m/ opened /o;
1068      # when optional parameters are not present in innfeed.conf
1069      return 1 if $left =~ m/ config: adding default value for key /o;
1070    }
1071    # hostChkCxn - now: x.xx, prev: x.xx, abs: xx, curr: x
1072    return 1 if $left =~ m/ hostChkCxn - now/o;
1073    # loading path_to_config_file/innfeed.conf
1074    return 1 if $left =~ m/loading /o;
1075    # Finally, to avoid problems with strange error lines, ignore them.
1076    #return 1 if ($left =~ /ME /);
1077  }
1078  ########
1079  ## innxmit
1080  if ($prog eq "innxmit") {
1081    # 437 Duplicate article
1082    if ($left =~ /(\S+) rejected [^\s]+ \(.*?\) (?:437|439) Duplicate article$/o) {
1083      my $server = $1;
1084      $server = lc $server unless $CASE_SENSITIVE;
1085      $innxmit_badart{$server}++;
1086      $innxmit_duplicate{$server}++;
1087      return 1;
1088    }
1089    # 437 Unapproved for
1090    if ($left =~ /(\S+) rejected [^\s]+ \(.*\) (?:437|439) Unapproved for \"(.*?)\"$/o) {
1091      my ($server, $group) = ($1, $2);
1092      $server = lc $server unless $CASE_SENSITIVE;
1093      $innxmit_badart{$server}++;
1094      $innxmit_unapproved{$server}++;
1095      $innxmit_unapproved_g{$group}++;
1096      return 1;
1097    }
1098    # 437 Too old -- ...
1099    if ($left =~ /(\S+) rejected [^\s]+ \(.*\) (?:437|439) Too old -- \".*?\"$/o) {
1100      my $server = $1;
1101      $server = lc $server unless $CASE_SENSITIVE;
1102      $innxmit_badart{$server}++;
1103      $innxmit_tooold{$server}++;
1104      return 1;
1105    }
1106    # 437 Unwanted site ... in path
1107    if ($left =~
1108      /(\S+) rejected [^\s]+ \(.*?\) (?:437|439) Unwanted site (\S+) in path$/o) {
1109      my ($server, $site) = ($1, $2);
1110      $server = lc $server unless $CASE_SENSITIVE;
1111      $innxmit_badart{$server}++;
1112      $innxmit_uw_site{$server}++;
1113      # $innxmit_site_path{$site}++;
1114      return 1;
1115    }
1116    # 437 Unwanted newsgroup "..."
1117    if ($left =~
1118      /(\S+) rejected [^\s]+ \(.*?\) (?:437|439) Unwanted newsgroup \"(\S+)\"$/o) {
1119      my ($server, $group) = ($1, $2);
1120      $server = lc $server unless $CASE_SENSITIVE;
1121      $innxmit_badart{$server}++;
1122      $innxmit_uw_ng_s{$server}++;
1123      $innxmit_uw_ng{$group}++;
1124      return 1;
1125    }
1126    # 437 Unwanted distribution "..."
1127    if ($left =~
1128      /(\S+) rejected [^\s]+ \(.*?\) (?:437|439) Unwanted distribution \"(\S+)\"$/o) {
1129      my ($server, $dist) = ($1, $2);
1130      $server = lc $server unless $CASE_SENSITIVE;
1131      $innxmit_badart{$server}++;
1132      $innxmit_uw_dist_s{$server}++;
1133      $innxmit_uw_dist{$dist}++;
1134      return 1;
1135    }
1136    # xx rejected foo.bar/12345 (foo/bar/12345) 437 Unwanted distribution "..."
1137    if ($left =~ /^(\S+) rejected .* (?:437|439) Unwanted distribution \"(\S+)\"$/o) {
1138      my ($server, $dist) = ($1, $2);
1139      $server = lc $server unless $CASE_SENSITIVE;
1140      $innxmit_badart{$server}++;
1141      $innxmit_uw_dist_s{$server}++;
1142      $innxmit_uw_dist{$dist}++;
1143      return 1;
1144    }
1145    # 437 Linecount x != y +- z
1146    if ($left =~ /(\S+) rejected [^\s]+ \(.*?\) (?:437|439) Linecount/o) {
1147      my $server = $1;
1148      $server = lc $server unless $CASE_SENSITIVE;
1149      $innxmit_badart{$server}++;
1150      $innxmit_linecount{$server}++;
1151      return 1;
1152    }
1153    # 437 Newsgroup name illegal -- "xxx"
1154    if ($left =~ /(\S+) rejected .* (?:437|439) Newsgroup name illegal -- "[^\"]*"$/) {
1155      my $server = $1;
1156      $server = lc $server unless $CASE_SENSITIVE;
1157      $innxmit_others{$server}++;
1158      $innxmit_badart{$server}++;
1159      return 1;
1160    }
1161    # Streaming retries
1162    return 1 if ($left =~ /\d+ Streaming retries$/o);
1163    # ihave failed
1164    if ($left =~ /(\S+) ihave failed/o) {
1165      my $server = $1;
1166      $server = lc $server unless $CASE_SENSITIVE;
1167      $innxmit_ihfail{$server} = 1;
1168      if ($left = /436 \S+ NNTP \S+ out of space/o) {
1169	$innxmit_nospace{$server}++;
1170	return 1;
1171      }
1172      if ($left = /400 \S+ space/o) {
1173	$innxmit_nospace{$server}++;
1174	return 1;
1175      }
1176      if ($left = /400 Bad file/o) {
1177	$innxmit_crefused{$server}++;
1178	return 1;
1179      }
1180      if ($left = /480 Transfer permission denied/o) {
1181	$innxmit_crefused{$server}++;
1182	return 1;
1183      }
1184    }
1185    # stats (new format)
1186    if ($left =~
1187      /(\S+) stats offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) accsize (\d+) rejsize (\d+)$/o) {
1188      my ($server, $offered, $accepted, $refused, $rejected, $missing, $accbytes, $rejbytes) =
1189	($1, $2, $3, $4, $5, $6, $7, $8);
1190      $server = lc $server unless $CASE_SENSITIVE;
1191      $innxmit_offered{$server} += $offered;
1192      $innxmit_offered{$server} -= $innxmit_ihfail{$server}
1193        if ($innxmit_ihfail{$server});
1194      $innxmit_accepted{$server} += $accepted;
1195      $innxmit_refused{$server} += $refused;
1196      $innxmit_rejected{$server} += $rejected;
1197      $innxmit_missing{$server} += $missing;
1198      $innxmit_accepted_size{$server} += $accbytes;
1199      $innxmit_rejected_size{$server} += $rejbytes;
1200      $innxmit_site{$server}++;
1201      $innxmit_ihfail{$server} = 0;
1202      return 1;
1203    }
1204    # stats
1205    if ($left =~
1206      /(\S+) stats offered (\d+) accepted (\d+) refused (\d+) rejected (\d+)$/o) {
1207      my ($server, $offered, $accepted, $refused, $rejected) =
1208	($1, $2, $3, $4, $5);
1209      $server = lc $server unless $CASE_SENSITIVE;
1210      $innxmit_offered{$server} += $offered;
1211      $innxmit_offered{$server} -= $innxmit_ihfail{$server}
1212        if ($innxmit_ihfail{$server});
1213      $innxmit_accepted{$server} += $accepted;
1214      $innxmit_refused{$server} += $refused;
1215      $innxmit_rejected{$server} += $rejected;
1216      $innxmit_site{$server}++;
1217      $innxmit_ihfail{$server} = 0;
1218      return 1;
1219    }
1220    # times
1221    if ($left =~ /(\S+) times user (.+) system (\S+) elapsed (\S+)$/o) {
1222      my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
1223      $server = lc $server unless $CASE_SENSITIVE;
1224      $innxmit_times{$server} += $elapsed;
1225      return 1;
1226    }
1227    # connect & no space
1228    if ($left =~ /(\S+) connect \S+ 400 No space/o) {
1229      my $server = $1;
1230      $server = lc $server unless $CASE_SENSITIVE;
1231      $innxmit_nospace{$server}++;
1232      $innxmit_site{$server}++;
1233      return 1;
1234    }
1235    # connect & NNTP no space
1236    if ($left =~ /(\S+) connect \S+ 400 \S+ out of space/o) {
1237      my $server = $1;
1238      $server = lc $server unless $CASE_SENSITIVE;
1239      $innxmit_nospace{$server}++;
1240      $innxmit_site{$server}++;
1241      return 1;
1242    }
1243    # connect & loadav
1244    if ($left =~ /(\S+) connect \S+ 400 loadav/o) {
1245      my $server = $1;
1246      if ($left =~ /expir/i) {
1247	$server = lc $server unless $CASE_SENSITIVE;
1248	$innxmit_expire{$server}++;
1249	$innxmit_site{$server}++;
1250	return 1;
1251      }
1252    }
1253    # connect 400 (other)
1254    if ($left =~ /(\S+) connect \S+ 400/o) {
1255      my $server = $1;
1256      $server = lc $server unless $CASE_SENSITIVE;
1257      $innxmit_crefused{$server}++;
1258      $innxmit_site{$server}++;
1259      return 1;
1260    }
1261    # connect failed
1262    if ($left =~ /(\S+) connect failed/o) {
1263      my $server = $1;
1264      $server = lc $server unless $CASE_SENSITIVE;
1265      $innxmit_cfail_host{$server}++;
1266      $innxmit_site{$server}++;
1267      return 1;
1268    }
1269    # authenticate failed
1270    if ($left =~ /(\S+) authenticate failed/o) {
1271      my $server = $1;
1272      $server = lc $server unless $CASE_SENSITIVE;
1273      $innxmit_afail_host{$server}++;
1274      $innxmit_site{$server}++;
1275      return 1;
1276    }
1277    # xxx ihave failed 400 loadav [innwatch:hiload] yyy gt zzz
1278    if ($left =~ /^(\S+) ihave failed 400 loadav/o) {
1279      my $server = $1;
1280      $server = lc $server unless $CASE_SENSITIVE;
1281      $innxmit_hiload{$server}++;
1282      return 1;
1283    }
1284    # ihave failed
1285    return 1 if ($left =~ /\S+ ihave failed/o);
1286    # requeued (....) 436 No space
1287    return 1 if ($left =~ /\S+ requeued \S+ 436 No space/o);
1288    # requeued (....) 400 No space
1289    return 1 if ($left =~ /\S+ requeued \S+ 400 No space/o);
1290    # requeued (....) 436 Can't write history
1291    return 1 if ($left =~ /\S+ requeued \S+ 436 Can\'t write history/o);
1292    # unexpected response code
1293    return 1 if ($left =~ /unexpected response code /o);
1294  }
1295
1296  ########
1297  ## nntplink
1298  if ($prog eq "nntplink") {
1299    $left =~ s/^(\S+):/$1/;
1300    # EOF
1301    if ($left =~ /(\S+) EOF /o) {
1302      my $server = $1;
1303      $server = lc $server unless $CASE_SENSITIVE;
1304      $nntplink_site{$server}++;
1305      $nntplink_eof{$server}++;
1306      return 1;
1307    }
1308    # Broken pipe
1309    if ($left =~ /(\S+) Broken pipe$/o) {
1310      my $server = $1;
1311      $server = lc $server unless $CASE_SENSITIVE;
1312      $nntplink_site{$server}++;
1313      $nntplink_bpipe{$server}++;
1314      return 1;
1315    }
1316    # already running - won't die
1317    return 1 if $left =~ /\S+ nntplink.* already running /o;
1318    # connection timed out
1319    if ($left =~ /(\S+) connection timed out/o) {
1320      my $server = $1;
1321      $server = lc $server unless $CASE_SENSITIVE;
1322      $nntplink_site{$server}++;
1323      $nntplink_bpipe{$server}++;
1324      return 1;
1325    }
1326    # greeted us with 400 No space
1327    if ($left =~ /(\S+) greeted us with 400 No space/o) {
1328      my $server = $1;
1329      $server = lc $server unless $CASE_SENSITIVE;
1330      $nntplink_site{$server}++;
1331      $nntplink_nospace{$server}++;
1332      return 1;
1333    }
1334    # greeted us with 400 loadav
1335    if ($left =~ /(\S+) greeted us with 400 loadav/o) {
1336      my $server = $1;
1337      $server = lc $server unless $CASE_SENSITIVE;
1338      $nntplink_site{$server}++;
1339      $nntplink_hiload{$server}++;
1340      return 1;
1341    }
1342    # greeted us with 400 (other)
1343    if ($left =~ /(\S+) greeted us with 400/o) {
1344      my $server = $1;
1345      $server = lc $server unless $CASE_SENSITIVE;
1346      $nntplink_site{$server}++;
1347      if ($left =~ /expir/i) {
1348	$nntplink_expire{$server}++;
1349      } else {
1350	$nntplink_fail{$server}++;
1351      }
1352      return 1;
1353    }
1354    # greeted us with 502
1355    if ($left =~ /(\S+) greeted us with 502/o) {
1356      my $server = $1;
1357      $server = lc $server unless $CASE_SENSITIVE;
1358      $nntplink_site{$server}++;
1359      $nntplink_auth{$server}++;
1360      return 1;
1361    }
1362    # sent authinfo
1363    if ($left =~ /(\S+) sent authinfo/o) {
1364      my $server = $1;
1365      $server = lc $server unless $CASE_SENSITIVE;
1366      $nntplink_site{$server}++;
1367      $nntplink_auth{$server}++;
1368      return 1;
1369    }
1370    # socket()
1371    if ($left =~ /(\S+) socket\(\): /o) {
1372      my $server = $1;
1373      $server = lc $server unless $CASE_SENSITIVE;
1374      $nntplink_site{$server}++;
1375      $nntplink_sockerr{$server}++;
1376      return 1;
1377    }
1378    # select()
1379    if ($left =~ /(\S+) select\(\) /o) {
1380      my $server = $1;
1381      $server = lc $server unless $CASE_SENSITIVE;
1382      $nntplink_site{$server}++;
1383      $nntplink_selecterr{$server}++;
1384      return 1;
1385    }
1386    # sent IHAVE
1387    if ($left =~ /(\S+) sent IHAVE/o) {
1388      my $server = $1;
1389      $server = lc $server unless $CASE_SENSITIVE;
1390      $nntplink_ihfail{$server}++;
1391      if (($left =~ / 436 /) && ($left =~ / out of space /)) {
1392	$nntplink_fake_connects{$server}++;
1393	$nntplink_nospace{$server}++;
1394      }
1395      return 1;
1396    }
1397    # article .... failed(saved): 436 No space
1398    if ($left =~ /(\S+) .* failed\(saved\): 436 No space$/o) {
1399      my $server = $1;
1400      $server = lc $server unless $CASE_SENSITIVE;
1401      $nntplink_nospace{$server}++;
1402      return 1;
1403    }
1404    # article .. 400 No space left on device writing article file -- throttling
1405    if ($left =~ /(\S+) .* 400 No space left on device writing article file -- throttling$/o) {
1406      my $server = $1;
1407      $server = lc $server unless $CASE_SENSITIVE;
1408      $nntplink_nospace{$server}++;
1409      return 1;
1410    }
1411    # stats
1412    if ($left =~ /(\S+) stats (\d+) offered (\d+) accepted (\d+) rejected (\d+) failed (\d+) connects$/o) {
1413      my ($server, $offered, $accepted, $rejected, $failed, $connects) =
1414	($1, $2, $3, $4, $5, $6);
1415      $server = lc $server unless $CASE_SENSITIVE;
1416      $nntplink_offered{$server} += $offered - $nntplink_ihfail{$server}++;
1417      $nntplink_accepted{$server} += $accepted;
1418      $nntplink_rejected{$server} += $rejected;
1419      $nntplink_failed{$server} += $failed;
1420      $nntplink_connects{$server} += $connects;
1421      $nntplink_ihfail{$server} = 0;
1422      if ($nntplink_fake_connects{$server}) {
1423	$nntplink_site{$server} += $nntplink_fake_connects{$server};
1424	$nntplink_fake_connects{$server} = 0;
1425      } else {
1426	$nntplink_site{$server}++;
1427      }
1428      return 1;
1429    }
1430    # xmit
1431    if ($left =~ /(\S+) xmit user (.+) system (\S+) elapsed (\S+)$/o) {
1432      my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
1433      $server = lc $server unless $CASE_SENSITIVE;
1434      $nntplink_times{$server} += $elapsed;
1435      return 1;
1436    }
1437    # xfer
1438    return 1 if $left =~ /\S+ xfer/o;
1439    # Links down .. x hours
1440    if ($left =~ /(\S+) Links* down \S+ \d+/o) {
1441      # Collected but not used
1442      # my $server = $1;
1443      # $server = lc $server unless $CASE_SENSITIVE;
1444      # $nntplink_down{$server} += $hours;
1445      return 1;
1446    }
1447    # 503 Timeout
1448    if ($left =~ /^(\S+) \S+ \S+ \S+ 503 Timeout/o) {
1449      # Collected but not used
1450      # my $server = $1;
1451      # $server = lc $server unless $CASE_SENSITIVE;
1452      # $nntplink_timeout{$server}++;
1453      return 1;
1454    }
1455    # read() error while reading reply
1456    if ($left =~ /^(\S+): read\(\) error while reading reply/o) {
1457      my $server = $1;
1458      $server = lc $server unless $CASE_SENSITIVE;
1459      $nntplink_failed{$server}++;
1460      return 1;
1461    }
1462    # Password file xxxx not found
1463    return 1 if $left =~ /^\S+ Password file \S+ not found/;
1464    # No such
1465    return 1 if $left =~ /^\S+ \S+ \S+ No such/;
1466    # already running
1467    return 1 if $left =~ /^\S+ \S+ already running/;
1468    # error reading version from datafile
1469    return 1 if $left =~ /error reading version from datafile/;
1470  }
1471  ########
1472  ## nnrpd
1473  if ($prog =~ /^nnrpd(?:-ssl)?$/)
1474  {
1475    # Fix a small bug of nnrpd (inn 1.4*)
1476    $left =~ s/^ /\? /o;
1477    # Another bug (in INN 1.5b1)
1478    return 1 if $left =~ /^\020\002m$/o; # ^P^Bm
1479    # bad_history at num for <ref>
1480    return 1 if $left =~ /bad_history at \d+ for /o;
1481    # timeout short
1482    return 1 if $left =~ /\S+ timeout short$/o;
1483    # < or > + (blablabla)
1484    return 1 if $left =~ /^\S+ [\<\>] /o;
1485    # cant opendir ... I/O error
1486    return 1 if $left =~ /\S+ cant opendir \S+ I\/O error$/o;
1487    # perl filtering enabled
1488    return 1 if $left =~ /perl filtering enabled$/o;
1489    # Python filtering enabled
1490    return 1 if $left =~ /Python filtering enabled$/o;
1491    return 1 if $left =~ /^python interpreter initialized OK$/o;
1492    return 1 if $left =~ /^python method \S+ not found$/o;
1493    return 1 if $left =~ /^python authenticate method succeeded, return code \d+, error string /o;
1494    return 1 if $left =~ /^python access method succeeded$/o;
1495    return 1 if $left =~ /^python dynamic method \(\w+ access\) succeeded, refuse string: /o;
1496    return 1 if $left =~ /^python: .+ module successfully hooked into nnrpd$/o;
1497    return 1 if $left =~ /^python: nnrpd .+ class instance created$/o;
1498    return 1 if $left =~ /^python: n_a authenticate\(\) invoked: hostname \S+, ipaddress \S+, interface \S+, user /o;
1499    return 1 if $left =~ /^python: n_a access\(\) invoked: hostname \S+, ipaddress \S+, interface \S+, user /o;
1500    return 1 if $left =~ /^python: n_a dynamic\(\) invoked against type \S+, hostname \S+, ipaddress \S+, interface \S+, user /o;
1501    return 1 if $left =~ /^python: authentication by username succeeded$/o;
1502    return 1 if $left =~ /^python: authentication by username failed$/o;
1503    return 1 if $left =~ /^python: authentication access by IP address succeeded$/o;
1504    return 1 if $left =~ /^python: authentication access by IP address failed$/o;
1505    return 1 if $left =~ /^python: dynamic access module successfully hooked into nnrpd$/o;
1506    return 1 if $left =~ /^python: dynamic authorization access for read access granted$/o;
1507    return 1 if $left =~ /^python: dynamic authorization access type is not known: /o;
1508    # during scanlogs
1509    return 1 if $left =~ /^\S+ rejected Flushing log and syslog files$/o;
1510    return 1 if $left =~ /^\S+ rejected Snapshot log and syslog files$/o;
1511    # other logs that should not be reported as errors
1512    return 1 if $left =~ /^\S+ auth also-log: /o;
1513    return 1 if $left =~ /^\S+ res also-log: /o;
1514    return 1 if $left =~ /^\S+ rejected by rule /o;
1515    # connect
1516    if ($left =~ /(\S+) (\([0-9a-fA-F:.]*\) )?connect(?: - port \d+)?$/o) {
1517      my $cust = $1;
1518      $cust = lc $cust unless $CASE_SENSITIVE;
1519      my $dom = &host2dom($cust);
1520      $nnrpd_dom_connect{$dom}++;
1521      $nnrpd_connect{$cust}++;
1522      return 1;
1523    }
1524    # group
1525    if ($left =~ /(\S+) group (\S+) (\d+)$/o) {
1526      my ($cust, $group, $num) = ($1, $2, $3);
1527      $cust = lc $cust unless $CASE_SENSITIVE;
1528      my $dom = &host2dom($cust);
1529
1530      if ($num) {
1531	$nnrpd_group{$group} += $num;
1532        $nnrpd_groups{$cust}++;
1533        $nnrpd_dom_groups{$dom}++;
1534
1535	my ($hierarchy) = $group =~ /^([^\.]+).*$/o;
1536	$nnrpd_hierarchy{$hierarchy} += $num;
1537      }
1538      return 1;
1539    }
1540    # post/ihave failed
1541    if ($left =~ /(\S+) (post|ihave) failed (.*)$/o) {
1542      my ($cust, $error) = ($1, $3);
1543      $cust = lc $cust unless $CASE_SENSITIVE;
1544      my $dom = &host2dom($cust);
1545      $nnrpd_dom_post_error{$dom}++;
1546      $nnrpd_post_error{$cust}++;
1547      return 1;
1548    }
1549    # post ok
1550    return 1 if $left =~ /\S+ post ok/o;
1551    # ihave ok
1552    return 1 if $left =~ /\S+ ihave ok/o;
1553    # posts
1554    if ($left =~ /(\S+) posts received (\d+) rejected (\d+)$/o) {
1555      my ($cust, $received, $rejected) = ($1, $2, $3);
1556      $cust = lc $cust unless $CASE_SENSITIVE;
1557      my $dom = &host2dom($cust);
1558      $nnrpd_dom_post_ok{$dom} += $received;
1559      $nnrpd_dom_post_rej{$dom} += $rejected;
1560      $nnrpd_post_ok{$cust} += $received;
1561      $nnrpd_post_rej{$cust} += $rejected;
1562      return 1;
1563    }
1564    # noperm post without permission
1565    if ($left =~ /(\S+) noperm post without permission/o) {
1566      my $cust = $1;
1567      $cust = lc $cust unless $CASE_SENSITIVE;
1568      my $dom = &host2dom($cust);
1569      $nnrpd_dom_post_rej{$dom} ++;
1570      $nnrpd_post_rej{$cust} ++;
1571      return 1;
1572    }
1573    # no_permission
1574    if ($left =~ /(\S+) no_(permission|access)$/o) {
1575      my $cust = $1;
1576      $cust = lc $cust unless $CASE_SENSITIVE;
1577      my $dom = &host2dom($cust);
1578      $nnrpd_no_permission{$cust}++;
1579      $nnrpd_dom_no_permission{$dom}++;
1580      return 1;
1581    }
1582    # bad_auth
1583    if ($left =~ /(\S+) bad_auth$/o) {
1584      my $cust = $1;
1585      $cust = lc $cust unless $CASE_SENSITIVE;
1586      my $dom = &host2dom($cust);
1587      $nnrpd_dom_no_permission{$dom}++;
1588      $nnrpd_no_permission{$cust}++;
1589      return 1;
1590    }
1591    # Authentication failure
1592    # User not known to the underlying authentication module
1593    return 1 if $left =~ / ckpasswd: pam_authenticate failed: /o;
1594    return 1 if $left =~ / ckpasswd: user .+ unknown$/o;
1595    # AUTHINFO (a username is a bytes string)
1596    if (($left =~ /\S+ user (.+)$/o) &&
1597        ($left !~ /\S+ times user .+ system \S+ idle \S+ elapsed \S+$/o)) {
1598      my $user = $1;
1599      $nnrpd_auth{$user}++;
1600      return 1;
1601    }
1602    # unrecognized + command
1603    if ($left =~ /(\S+) unrecognized (.*)$/o) {
1604      my ($cust, $error) = ($1, $2);
1605      $cust = lc $cust unless $CASE_SENSITIVE;
1606      my $dom = &host2dom($cust);
1607      $error = "_null command_" if ($error !~ /\S/);
1608      $error =~ s/^(xmotd) .*$/$1/i if ($error =~ /^xmotd .*$/i);
1609      $nnrpd_dom_unrecognized{$dom}++;
1610      $nnrpd_unrecognized{$cust}++;
1611      $nnrpd_unrecogn_cmd{$error}++;
1612      return 1;
1613    }
1614    # exit (also called when using STARTTLS)
1615    if ($left =~ /(\S+) (?:exit|exit for STARTTLS|exit for AUTHINFO SASL) articles (\d+) groups (\d+)$/o) {
1616      my ($cust, $articles, $groups) = ($1, $2, $3);
1617      $cust = lc $cust unless $CASE_SENSITIVE;
1618      my $dom = &host2dom($cust);
1619      if ($cust eq '?') {
1620        $nnrpd_connect{$cust}++;
1621        $nnrpd_dom_connect{$dom}++;
1622      }
1623      $nnrpd_articles{$cust} += $articles;
1624      $nnrpd_dom_articles{$dom} += $articles;
1625      return 1;
1626    }
1627    # times
1628    if ($left =~ /(\S+) times user (.+) system (\S+) idle (\S+) elapsed (\S+)$/o) {
1629      my ($cust, $user, $system, $idle, $elapsed) = ($1, $2, $3, $4, $5);
1630      $cust = lc $cust unless $CASE_SENSITIVE;
1631      my $dom = &host2dom($cust);
1632      $nnrpd_times{$cust} += $elapsed;
1633      $nnrpd_resource_user{$cust} += $user;
1634      $nnrpd_resource_system{$cust} += $system;
1635      $nnrpd_resource_idle{$cust} += $idle;
1636      $nnrpd_resource_elapsed{$cust} += $elapsed;
1637      $nnrpd_dom_times{$dom} += $elapsed;
1638      return 1;
1639    }
1640    # artstats
1641    if ($left =~ /(\S+) artstats get (\d+) time (\d+) size (\d+)$/o) {
1642      my ($cust, $articles, $time, $bytes) = ($1, $2, $3, $4);
1643      $cust = lc $cust unless $CASE_SENSITIVE;
1644      my $dom = &host2dom($cust);
1645      $nnrpd_bytes{$cust} += $bytes;
1646      $nnrpd_dom_bytes{$dom} += $bytes;
1647      return 1;
1648    }
1649    # timeout
1650    if ($left =~ /(\S+) timeout$/o) {
1651      my $cust = $1;
1652      $cust = lc $cust unless $CASE_SENSITIVE;
1653      my $dom = &host2dom($cust);
1654      $nnrpd_dom_timeout{$dom}++;
1655      $nnrpd_timeout{$cust}++;
1656      return 1;
1657    }
1658    # timeout in post
1659    if ($left =~ /(\S+) timeout in post$/o) {
1660      my $cust = $1;
1661      $cust = lc $cust unless $CASE_SENSITIVE;
1662      my $dom = &host2dom($cust);
1663      $nnrpd_dom_timeout{$dom}++;
1664      $nnrpd_timeout{$cust}++;
1665      return 1;
1666    }
1667    # can't read: Connection timed out
1668    if ($left =~ /(\S+) can\'t read: Connection timed out$/o) {
1669      my $cust = $1;
1670      $cust = lc $cust unless $CASE_SENSITIVE;
1671      my $dom = &host2dom($cust);
1672      $nnrpd_dom_timeout{$dom}++;
1673      $nnrpd_timeout{$cust}++;
1674      return 1;
1675    }
1676    # can't read: Operation timed out
1677    if ($left =~ /(\S+) can\'t read: Operation timed out$/o) {
1678      my $cust = $1;
1679      $cust = lc $cust unless $CASE_SENSITIVE;
1680      my $dom = &host2dom($cust);
1681      $nnrpd_dom_timeout{$dom}++;
1682      $nnrpd_timeout{$cust}++;
1683      return 1;
1684    }
1685    # can't read: Connection reset by peer
1686    if ($left =~ /(\S+) can\'t read: Connection reset by peer$/o) {
1687      my $cust = $1;
1688      $cust = lc $cust unless $CASE_SENSITIVE;
1689      my $dom = &host2dom($cust);
1690      $nnrpd_dom_reset_peer{$dom}++;
1691      $nnrpd_reset_peer{$cust}++;
1692      return 1;
1693    }
1694    # can't read: Network is unreachable
1695    return 1 if $left =~ /(\S+) can\'t read: Network is unreachable$/o;
1696    # gethostbyaddr: xxx.yyy.zzz != a.b.c.d
1697    if ($left =~ /^gethostbyaddr: (.*)$/o) {
1698      my $msg = $1;
1699      $nnrpd_gethostbyaddr{$msg}++;
1700      return 1;
1701    }
1702    # can't gethostbyaddr
1703    if ($left =~ /\? can\'t gethostbyaddr (\S+) .*$/o) {
1704      my $ip = $1;
1705      $nnrpd_gethostbyaddr{$ip}++;
1706      return 1;
1707    }
1708    # can't getpeername
1709    if ($left =~ /\? can\'t getpeername/o) {
1710      $nnrpd_gethostbyaddr{"? (can't getpeername)"}++;
1711      return 1;
1712    }
1713    # can't getsockname
1714    return 1 if $left =~ /^\S+ can\'t getsockname$/o;
1715    # can't initialize TLS session
1716    return 1 if $left =~ /^\S+ failure to negotiate TLS session$/o;
1717    # reverse lookup failed
1718    return 1 if $left =~ /^\? reverse lookup for \S+ failed: .* -- using IP address for access$/o;
1719    # profile timer
1720    # ME time X nnnn X(X) [...]
1721    # The exact timers change from various versions of INN, so try to deal
1722    # with this in a general fashion.
1723    if ($left =~ m/^\S+\s+                         # ME
1724	           time\s(\d+)\s+                  # time
1725                   ((?:\S+\s\d+\(\d+\)\s*)+)       # timer values
1726                   $/ox) {
1727      $nnrpd_time_times += $1;
1728      my $timers = $2;
1729
1730      while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
1731        my $name = $nnrpd_timer_names{$1} || $1;
1732        $nnrpd_time_time{$name} += $2;
1733        if ($3) {
1734	  my $average = $2 / $3;
1735	  $nnrpd_time_num{$name} += $3;
1736          my $min = $nnrpd_time_min{$name};
1737          $nnrpd_time_min{$name} = $average
1738            if (!defined($min) || $min > $average);
1739          my $max = $nnrpd_time_max{$name};
1740          $nnrpd_time_max{$name} = $average
1741            if (!defined($max) || $max < $average);
1742        }
1743      }
1744      return 1;
1745    }
1746    # ME dropping articles into ...
1747    return 1 if $left =~ /ME dropping articles into /o;
1748    # newnews (interesting but ignored till now)
1749    return 1 if $left =~ /^\S+ newnews /o;
1750    # cant fopen (ignored too)
1751    return 1 if $left =~ /^\S+ cant fopen /o;
1752    # can't read: No route to host
1753    return 1 if $left =~ /can\'t read: No route to host/o;
1754    # can't read: Broken pipe
1755    return 1 if $left =~ /can\'t read: Broken pipe/o;
1756    # eof in post
1757    return 1 if $left =~ /^\S+ EOF in post$/o;
1758    # ioctl: ...
1759    return 1 if $left =~ /^ioctl: /o;
1760    # other stats
1761    return 1 if $left =~ /^\S+ overstats count \d+ hit \d+ miss \d+ time \d+ size \d+ dbz \d+ seek \d+ get \d+ artcheck \d+$/o;
1762    # starttls
1763    return 1 if $left =~ /^starttls: \S+ with cipher \S+ \(\d+\/\d+ bits\) no authentication$/o;
1764  }
1765  ########
1766  ## overchan
1767  if ($prog eq "overchan") {
1768    # times
1769    if ($left =~ /timings (\d+) arts (\d+) of (\d+) ms$/o) {
1770      my ($articles, $work_time, $run_time) = ($1, $2, $3);
1771      # ??? What to do with numbers
1772      return 1;
1773    }
1774  }
1775  ########
1776  ## batcher
1777  if ($prog eq "batcher") {
1778    # times
1779    if ($left =~ /(\S+) times user (.+) system (\S+) elapsed (\S+)$/o) {
1780      my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
1781      $server = lc $server unless $CASE_SENSITIVE;
1782      # $batcher_user{$server} += $user;
1783      # $batcher_system{$server} += $system;
1784      $batcher_elapsed{$server} += $elapsed;
1785      return 1;
1786    }
1787    # stats
1788    if ($left =~ /(\S+) stats batches (\d+) articles (\d+) bytes (\d+)$/o) {
1789      my ($server, $batches, $articles, $bytes) = ($1, $2, $3, $4);
1790      $server = lc $server unless $CASE_SENSITIVE;
1791      $batcher_offered{$server} += $batches;
1792      $batcher_articles{$server} += $articles;
1793      $batcher_bytes{$server} += $bytes;
1794      return 1;
1795    }
1796  }
1797  ########
1798  ## rnews
1799  if ($prog eq "rnews") {
1800    # rejected connection
1801    if ($left =~ /rejected connection (.*)$/o) {
1802      $rnews_rejected{$1}++;
1803      return 1;
1804    }
1805    # cant open_remote
1806    if ($left =~ /(cant open_remote .*)$/o) {
1807      $rnews_rejected{$1}++;
1808      return 1;
1809    }
1810    # rejected 437 Unwanted newsgroup
1811    if ($left =~ /rejected (?:437|439) Unwanted newsgroup \"(.*)\"$/o) {
1812      $rnews_bogus_ng{$1}++;
1813      return 1;
1814    }
1815    # rejected 437 Unapproved for "xx"
1816    if ($left =~ /rejected (?:437|439) Unapproved for \"(.*)\"$/o) {
1817      $rnews_unapproved{$1}++;
1818      return 1;
1819    }
1820    # rejected 437 Unwanted distribution
1821    if ($left =~ /rejected (?:437|439) Unwanted distribution (.*)$/o) {
1822      $rnews_bogus_dist{$1}++;
1823      return 1;
1824    }
1825    # rejected 437 Bad "Date"
1826    if ($left =~ /rejected (?:437|439) Bad \"Date\" (.*)$/o) {
1827      $rnews_bogus_date{$1}++;
1828      return 1;
1829    }
1830    # rejected 437 Bad "Injection-Date"
1831    if ($left =~ /rejected (?:437|439) Bad \"Injection-Date\" (.*)$/o) {
1832      $rnews_bogus_date{$1}++;
1833      return 1;
1834    }
1835    # rejected 437 Article injected or posted in the future
1836    if ($left =~ /rejected (?:437|439) Article injected or posted in the future -- \"(.*)\"$/o) {
1837      $rnews_bogus_date{"(future) $1"}++;
1838      return 1;
1839    }
1840    # rejected 437 Too old -- "..."
1841    if ($left =~ /rejected (?:437|439) Too old -- (.*)$/o) {
1842      $rnews_too_old++;
1843      return 1;
1844    }
1845    # rejected 437 Linecount...
1846    if ($left =~ /rejected (?:437|439) Linecount \d+ \!= \d+/o) {
1847      $rnews_linecount++;
1848      return 1;
1849    }
1850    # rejected 437 Duplicate
1851    if ($left =~ /rejected (?:437|439) Duplicate$/o) {
1852      $rnews_duplicate++;
1853      return 1;
1854    }
1855    # rejected 437 Duplicate article
1856    if ($left =~ /rejected (?:437|439) Duplicate article/o) {
1857      $rnews_duplicate++;
1858      return 1;
1859    }
1860    # rejected 437 No colon-space ...
1861    if ($left =~ /rejected (?:437|439) No colon-space in \"(.*)\" header$/o) {
1862      $rnews_no_colon_space++;
1863      return 1;
1864    }
1865    # duplicate <msg-id> path..
1866    if ($left =~ /^duplicate /o) {
1867      $rnews_duplicate++;
1868      return 1;
1869    }
1870    # offered <msg-id> feed
1871    if ($left =~ /^offered \S+ (\S+)/o) {
1872      my $host = $1;
1873      $host = lc $host unless $CASE_SENSITIVE;
1874      # Small hack used to join article spooled when innd is throttle.
1875      # In this situation, the hostname is a 8 hex digits string
1876      # To avoid confusions with real feeds, the first character is forced
1877      # to be a '3' or a '4' (will work between 9/7/1995 and 13/7/2012).
1878      $host = "Local postings" if $host =~ /^[34][0-9a-f]{7}$/;
1879      $rnews_host{$host}++;
1880      return 1;
1881    }
1882    # rejected 437 "Subject" header too long
1883    return 1 if $left =~ m/header too long/o;
1884    # rejected 437 Reason...
1885    return 1 if $left =~ m/rejected (?:437|439)/o;
1886    # bad_article missing (Message-ID|Path|...)
1887    return 1 if $left =~ m/bad_article missing /o;
1888    # cant unspool saving to xxx
1889    return 1 if $left =~ m/cant unspool saving to/o;
1890  }
1891
1892  ###########
1893  ## ncmspool
1894  if ($prog eq "ncmspool") {
1895    # <article> good signature from foo@bar.com
1896    if ($left =~ /good signature from (.*)/o) {
1897      $nocem_goodsigs{$1}++;
1898      $nocem_totalgood++;
1899      $nocem_lastid = $1;
1900      return 1;
1901    }
1902    # <article> bad signature from foo@bar.com
1903    if ($left =~ /bad signature from (.*)/o) {
1904      $nocem_badsigs{$1}++;
1905      $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
1906      $nocem_totalbad++;
1907      $nocem_lastid = $1;
1908      return 1;
1909    }
1910    # <article> contained 123 new 456 total ids
1911    if ($left =~ /contained (\d+) new (\d+) total ids/o) {
1912      $nocem_newids += $1;
1913      $nocem_newids{$nocem_lastid} += $1;
1914      $nocem_totalids += $2;
1915      $nocem_totalids{$nocem_lastid} += $2;
1916      return 1;
1917    }
1918    return 1;
1919  }
1920
1921  ########
1922  ## nocem
1923  if ($prog eq "nocem") {
1924    if ($left =~ /processed notice .* by (.*) \((\d+) ids,/o) {
1925      $nocem_goodsigs{$1}++;
1926      $nocem_totalgood++;
1927      $nocem_lastid = $1;
1928      $nocem_newids += $2;
1929      $nocem_newids{$nocem_lastid} += $2;
1930      $nocem_totalids += $2;
1931      $nocem_totalids{$nocem_lastid} += $2;
1932      return 1;
1933    }
1934    if ($left =~ /Article <[^>]*>: (.*) \(ID [[:xdigit:]]*\) not in keyring/o) {
1935      $nocem_badsigs{$1}++;
1936      $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
1937      $nocem_totalids{$1} = 0 unless ($nocem_totalids{$1});
1938      $nocem_totalbad++;
1939      $nocem_lastid = $1;
1940      return 1;
1941    }
1942    if ($left =~ /Article <[^>]*>: bad signature from (.*)/o) {
1943      $nocem_badsigs{$1}++;
1944      $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
1945      $nocem_totalids{$1} = 0 unless ($nocem_totalids{$1});
1946      $nocem_totalbad++;
1947      $nocem_lastid = $1;
1948      return 1;
1949    }
1950    if ($left =~ /Article <[^>]*>: malformed signature/o) {
1951      $nocem_badsigs{'N/A'}++;
1952      $nocem_goodsigs{'N/A'} = 0 unless ($nocem_goodsigs{'N/A'});
1953      $nocem_totalids{'N/A'} = 0 unless ($nocem_totalids{'N/A'});
1954      $nocem_totalbad++;
1955      $nocem_lastid = 'N/A';
1956      return 1;
1957    }
1958
1959    return 1;
1960  }
1961
1962  ###########
1963  ## controlchan
1964  if ($prog eq "controlchan") {
1965    # loaded /x/y/z/foo.pl
1966    return 1 if $left =~ m/^loaded /;
1967    # starting
1968    return 1 if $left =~ m/^starting/;
1969    # skipping rmgroup x@y (pgpverify failed) in <foo@bar>
1970    if ($left =~ m/^skipping \S+ (\S+) \(pgpverify failed\) in /) {
1971      $controlchan_skippgp{$1}++;
1972      $controlchan_who{$1}++;
1973      return 1;
1974    }
1975    if ($left =~ m/^control_(sendme|ihave), [^,]+, (\S+), doit,/o) {
1976      if ($1 eq "sendme") {
1977	$controlchan_sendme_site{$2}++;
1978      } else {
1979	$controlchan_ihave_site{$2}++;
1980      }
1981      return 1;
1982    }
1983    # control_XXgroup, foo.bar [moderated] who who token, [pattern], [pattern], encoding, peer, action, 1
1984    #
1985    # Various other random junk can end up in the moderated field, like y,
1986    # unmoderated, m, etc. depending on what the control message says.  It
1987    # can even have multiple words, which we still don't handle.
1988    if ($left =~ m/^control_(\S+),    # type of msg
1989                  \s(?:\S+)?          # newsgroup name
1990                  (\s\S+)?            # optional
1991                  \s(\S+)             # e-mail
1992                  \s\S+               # e-mail
1993                  \s\S+,              # storage token
1994                  \s(?:\S+)?,         # exclusion pattern
1995                  \s(?:\S+)?,         # drop pattern
1996                  \s\S+,              # local encoding
1997                  \s\S+,              # server
1998                  \s([^=,]+(?:=\S+)?),            # action
1999                  \s*(.*)             # 1 if message approved and first logged in the file
2000                  /x) {
2001      if ($1 eq 'newgroup') {
2002	$controlchan_new{$3}++;
2003      } elsif ($1 eq 'rmgroup') {
2004	$controlchan_rm{$3}++;
2005      } else {
2006	$controlchan_other{$3}++ if $5 >= 0;
2007      }
2008      $controlchan_who{$3}++;
2009      $controlchan_ok{$3} += $5 if $5 > 0;
2010      my $action = $4;
2011      my $email = $3;
2012      $action =~ s/=.*//;
2013      $controlchan_doit{$email}++ if $action eq 'doit';
2014      return 1;
2015    }
2016    # checkgroups processed or not (with no change or not)
2017    return 1 if $left =~ /^checkgroups by \S+/o;
2018  }
2019
2020  ###########
2021  ## cnfsstat
2022  if ($prog eq "cnfsstat") {
2023    # Class ALT for groups matching "alt.*" article size min/max: 0/1048576
2024    # Buffer T3, len: 1953  Mbytes, used: 483.75 Mbytes (24.8%)   0 cycles
2025    if ($left =~ m|^Class\ (\S+)\ for\ groups\ matching\ \S+
2026                    (\ article\ size\ min/max:\ \d+/\d+)?
2027                    \ Buffer\ (\S+),
2028                    \ len:\ ([\d.]+)\s+Mbytes,
2029                    \ used:\ ([\d.]+)\ Mbytes\ \(\s*[\d.]+%\)
2030                    \s+(\d+)\ cycles\s*
2031                 $|ox) {
2032      my ($class, $buffer, $size, $used, $cycles) = ($1, $3, $4, $5, $6);
2033      my ($h, $m, $s) = $hour =~ m/^(\d+):(\d+):(\d+)$/;
2034      my $time = $h * 3600 + $m * 60 + $s;
2035      $size *= 1024 * 1024;
2036      $used *= 1024 * 1024;
2037      $cnfsstat{$buffer} = $class;
2038
2039      # If the size changed, invalidate all of our running fill rate stats.
2040      if (!exists($cnfsstat_size{$buffer}) ||  $size != $cnfsstat_size{$buffer}) {
2041        delete $cnfsstat_rate{$buffer};
2042        delete $cnfsstat_samples{$buffer};
2043        delete $cnfsstat_time{$buffer};
2044        $cnfsstat_size{$buffer} = $size;
2045      }
2046      elsif ($cnfsstat_time{$buffer}) {
2047        # We want to gather the rate at which cycbuffs fill.  Store a
2048        # running total of bytes/second and a total number of samples.
2049        # Ideally we'd want a weighted average of those samples by the
2050        # length of the sample period, but we'll ignore that and assume
2051        # cnfsstat runs at a roughly consistent interval.
2052        my ($period, $added);
2053        $period = $time - $cnfsstat_time{$buffer};
2054        $period = 86400 - $cnfsstat_time{$buffer} + $time if $period <= 0;
2055        $added = $used - $cnfsstat_used{$buffer};
2056        if ($cycles > $cnfsstat_cycles{$buffer}) {
2057          $added += $size * ($cycles - $cnfsstat_cycles{$buffer});
2058        }
2059        if ($added > 0) {
2060          $cnfsstat_rate{$buffer} += $added / $period;
2061          $cnfsstat_samples{$buffer}++;
2062        }
2063      }
2064      $cnfsstat_used{$buffer} = $used;
2065      $cnfsstat_cycles{$buffer} = $cycles;
2066      $cnfsstat_time{$buffer} = $time;
2067      return 1;
2068    }
2069  }
2070
2071  # Ignore following programs :
2072  return 1 if ($prog eq "uxfxn");
2073  return 1 if ($prog eq "beverage");
2074  return 1 if ($prog eq "newsx");
2075  return 1 if ($prog eq "demmf");
2076  return 1 if ($prog eq "nnnn");
2077  return 1 if ($prog eq "slurp");
2078  return 0;
2079}
2080
2081#################################
2082# Adjust some values..
2083
2084sub adjust($$) {
2085  my ($first_date, $last_date) = @_;
2086
2087  my $nnrpd_doit = 0;
2088  my $curious;
2089
2090  {
2091    my $serv;
2092    if (%nnrpd_connect) {
2093      my @keys = keys (%nnrpd_connect);
2094      my $c = @keys;
2095      foreach my $serv (@keys) {
2096	if ($nnrpd_no_permission{$serv}) {
2097	  my $dom = &host2dom($serv);
2098	  $nnrpd_dom_connect{$dom} -= $nnrpd_connect{$serv}
2099	    if defined $nnrpd_dom_connect{$dom};
2100	  $nnrpd_dom_times{$dom}   -= $nnrpd_times{$serv}
2101	    if defined $nnrpd_dom_times{$dom};
2102
2103          # The message "bad_auth" can occur more than once per session.
2104          # Subtracting nnrpd_no_permission from nnrpd_connect is
2105          # broken and can yield negative values for nnrpd_connect.
2106	  $nnrpd_connect{$serv} -= $nnrpd_no_permission{$serv};
2107
2108          # Perl considers negative values to be true.  Previously the
2109          # hash entry was deleted only if the value was exactly 0.
2110          delete $nnrpd_connect{$serv} unless $nnrpd_connect{$serv} > 0;
2111
2112	  delete $nnrpd_groups{$serv}  unless $nnrpd_groups{$serv};
2113	  delete $nnrpd_times{$serv}   unless $nnrpd_times{$serv};
2114	  delete $nnrpd_usr_times{$serv}   unless $nnrpd_usr_times{$serv};
2115	  delete $nnrpd_sys_times{$serv}   unless $nnrpd_sys_times{$serv};
2116	  delete $nnrpd_dom_connect{$dom} unless $nnrpd_dom_connect{$dom} > 0;
2117	  delete $nnrpd_dom_groups{$dom}  unless $nnrpd_dom_groups{$dom};
2118	  delete $nnrpd_dom_times{$dom}   unless $nnrpd_dom_times{$dom};
2119	  $c--;
2120	}
2121	$nnrpd_doit++
2122	  if $nnrpd_groups{$serv} || $nnrpd_post_ok{$serv};
2123      }
2124      undef %nnrpd_connect unless $c;
2125    }
2126    foreach my $serv (keys (%nnrpd_groups)) {
2127      $curious = "ok" unless $nnrpd_groups{$serv} || $nnrpd_post_ok{$serv} ||
2128	$nnrpd_articles{$serv};
2129    }
2130  }
2131
2132  # Fill some hashes
2133  {
2134    my ($key, $hostname, $channel);
2135
2136    # Since the checkpoint counts include entries for all server
2137    # connections, check to see if any checkpoint server entries are not also
2138    # in %innd_connect.  Add any missing servers (persistant servers with no
2139    # connected log lines) to %innd_connect so that incoming totals will be
2140    # properly computed.
2141    foreach $server (keys (%innd_accepted)) {
2142      if (! defined($innd_connect{$server})) {
2143        $innd_connect{$server} = 0;
2144      }
2145    }
2146
2147    foreach $key (keys (%innd_connect)) {
2148      $innd_offered{$key} = ($innd_accepted{$key} || 0)
2149	+ ($innd_refused{$key} || 0)
2150	+ ($innd_rejected{$key} || 0);
2151      $innd_offered_size{$key} = ($innd_stored_size{$key} || 0)
2152	+ ($innd_duplicated_size{$key} || 0) + ($innd_rejected_size{$key} || 0);
2153    }
2154
2155    # Sum all incoming traffic for each full server.
2156    foreach $key (keys (%innd_connect)) {
2157      if ($key =~ /^(\S+):\d+$/) {
2158        $innd_seconds_sum{$1} += ($innd_seconds{$key} || 0);
2159        $innd_connect_sum{$1} += ($innd_connect{$key} || 0);
2160        $innd_offered_sum{$1} += ($innd_offered{$key} || 0);
2161        $innd_accepted_sum{$1} += ($innd_accepted{$key} || 0);
2162        $innd_refused_sum{$1} += ($innd_refused{$key} || 0);
2163        $innd_rejected_sum{$1} += ($innd_rejected{$key} || 0);
2164        $innd_stored_size_sum{$1} += ($innd_stored_size{$key} || 0);
2165        $innd_duplicated_size_sum{$1} += ($innd_duplicated_size{$key} || 0);
2166        $innd_offered_size_sum{$1} += ($innd_offered_size{$key} || 0);
2167        $innd_rejected_size_sum{$1} += ($innd_rejected_size{$key} || 0);
2168      }
2169    }
2170
2171    # adjust min/max of innd timer stats.
2172    if (%innd_time_min) {
2173      foreach $key (keys (%innd_time_min)) {
2174	$innd_time_min{$key} = 0 if ($innd_time_min{$key} == $MIN);
2175	$innd_time_max{$key} = 0 if ($innd_time_max{$key} == $MAX);
2176
2177	#$innd_time_min{$key} /= 1000;
2178	#$innd_time_max{$key} /= 1000;
2179      }
2180    }
2181    if (%innfeed_time_min) {
2182      foreach $key (keys (%innfeed_time_min)) {
2183        $innfeed_time_min{$key} = 0 if ($innfeed_time_min{$key} == $MIN);
2184        $innfeed_time_max{$key} = 0 if ($innfeed_time_max{$key} == $MAX);
2185      }
2186    }
2187    if (%nnrpd_time_min) {
2188      foreach $key (keys (%nnrpd_time_min)) {
2189        $nnrpd_time_min{$key} = 0 if ($nnrpd_time_min{$key} == $MIN);
2190        $nnrpd_time_max{$key} = 0 if ($nnrpd_time_max{$key} == $MAX);
2191      }
2192    }
2193    # remove the innd timer stats if not used.
2194    unless ($innd_time_times) {
2195      undef %innd_time_min;
2196      undef %innd_time_max;
2197      undef %innd_time_num;
2198      undef %innd_time_time;
2199    }
2200    # same thing for innfeed timer
2201    unless ($innfeed_time_times) {
2202      undef %innfeed_time_min;
2203      undef %innfeed_time_max;
2204      undef %innfeed_time_num;
2205      undef %innfeed_time_time;
2206    }
2207    # same thing for nnrpd timer
2208    unless ($nnrpd_time_times) {
2209      undef %nnrpd_time_min;
2210      undef %nnrpd_time_max;
2211      undef %nnrpd_time_num;
2212      undef %nnrpd_time_time;
2213    }
2214  }
2215
2216  if (%inn_flow) {
2217    my ($prev_dd, $prev_d, $prev_h) = ("", -1, -1);
2218    my $day;
2219    foreach $day (sort datecmp keys (%inn_flow)) {
2220      my ($r, $h) = $day =~ /^(.*) (\d+)$/;
2221      my $d = index ("JanFebMarAprMayJunJulAugSepOctNovDec",
2222		     substr ($r,0,3)) / 3 * 31 + substr ($r, 4, 2);
2223      $prev_h = $h if ($prev_h == -1);
2224      if ($prev_d == -1) {
2225	$prev_d = $d;
2226	$prev_dd = $r;
2227      }
2228      if ($r eq $prev_dd) { # Same day and same month ?
2229	if ($h != $prev_h) {
2230	  if ($h == $prev_h + 1) {
2231	    $prev_h++;
2232	  }
2233	  else {
2234	    my $j;
2235	    for ($j = $prev_h + 1; $j < $h; $j++) {
2236	      my $t = sprintf "%02d", $j;
2237	      $inn_flow{"$r $t"} = 0;
2238	    }
2239	    $prev_h = $h;
2240	  }
2241	}
2242      }
2243      else {
2244	my $j;
2245	# then end of the first day...
2246	for ($j = ($prev_h == 23) ? 24 : $prev_h + 1; $j < 24; $j++) {
2247	  my $t = sprintf "%02d", $j;
2248	  $inn_flow{"$prev_dd $t"} = 0;
2249	}
2250
2251	# all the days between (if any)
2252	# well, we can forget them as it is supposed to be a tool
2253	# launched daily.
2254
2255	# the beginning of the last day..
2256	for ($j = 0; $j < $h; $j++) {
2257	  my $t = sprintf "%02d", $j;
2258	  $inn_flow{"$r $t"} = 0;
2259	}
2260	$prev_dd = $r;
2261	$prev_d = $d;
2262	$prev_h = $h;
2263      }
2264    }
2265    my $first = 1;
2266    my (%hash, %hash_time, %hash_size, $date, $delay);
2267    foreach $day (sort datecmp keys (%inn_flow)) {
2268      my ($r, $h) = $day =~ /^(.*) (\d+)$/o;
2269      if ($first) {
2270	$first = 0;
2271	my ($t) = $first_date =~ m/:(\d\d:\d\d)$/o;
2272	$date = "$day:$t - $h:59:59";
2273	$t =~ m/(\d\d):(\d\d)/o;
2274	$delay = 3600 - $1 * 60 - $2;
2275      }
2276      else {
2277	$date = "$day:00:00 - $h:59:59";
2278	$delay = 3600;
2279      }
2280      $hash{$date} = $inn_flow{$day};
2281      $hash_size{$date} = $inn_flow_size{$day};
2282      $inn_flow_labels{$date} = $h;
2283      $hash_time{$date} = $delay;
2284    }
2285    my ($h, $t) = $last_date =~ m/ (\d+):(\d\d:\d\d)$/o;
2286    my ($h2) = $date =~ m/ (\d+):\d\d:\d\d /o;
2287    my $date2 = $date;
2288    $date2 =~ s/$h2:59:59$/$h:$t/;
2289    $hash{$date2} = $hash{$date};
2290    delete $hash{"$date"};
2291    $hash_size{$date2} = $hash_size{$date};
2292    delete $hash_size{"$date"};
2293    $t =~ m/(\d\d):(\d\d)/o;
2294    $hash_time{$date2} = $hash_time{$date} - ($h2 == $h) * 3600 + $1 * 60 + $2;
2295    delete $hash_time{"$date"};
2296    $inn_flow_labels{$date2} = $h;
2297    %inn_flow = %hash;
2298    %inn_flow_time = %hash_time;
2299    %inn_flow_size = %hash_size;
2300  }
2301
2302  if (%innd_bad_ihave) {
2303    my $key;
2304    my $msg = 'Bad ihave control messages received';
2305    foreach $key (keys %innd_bad_ihave) {
2306      $innd_misc_stat{$msg}{$key} = $innd_bad_ihave{$key};
2307    }
2308  }
2309  if (%innd_bad_msgid) {
2310    my $key;
2311    my $msg = 'Bad Message-ID\'s offered';
2312    foreach $key (keys %innd_bad_msgid) {
2313      $innd_misc_stat{$msg}{$key} = $innd_bad_msgid{$key};
2314    }
2315  }
2316  if (%innd_bad_sendme) {
2317    my $key;
2318    my $msg = 'Ignored sendme control messages received';
2319    foreach $key (keys %innd_bad_sendme) {
2320      $innd_misc_stat{$msg}{$key} = $innd_bad_sendme{$key};
2321    }
2322  }
2323  if (%innd_bad_command) {
2324    my $key;
2325    my $msg = 'Bad command received';
2326    foreach $key (keys %innd_bad_command) {
2327      $innd_misc_stat{$msg}{$key} = $innd_bad_command{$key};
2328    }
2329  }
2330  if (%innd_bad_newsgroup) {
2331    my $key;
2332    my $msg = 'Bad newsgroups received';
2333    foreach $key (keys %innd_bad_newsgroup) {
2334      $innd_misc_stat{$msg}{$key} = $innd_bad_newsgroup{$key};
2335    }
2336  }
2337  if (%innd_posted_future) {
2338    my $key;
2339    my $msg = 'Article posted in the future';
2340    foreach $key (keys %innd_posted_future) {
2341      $innd_misc_stat{$msg}{$key} = $innd_posted_future{$key};
2342    }
2343  }
2344  if (%innd_no_colon_space) {
2345    my $key;
2346    my $msg = 'No colon-space in header';
2347    foreach $key (keys %innd_no_colon_space) {
2348      $innd_misc_stat{$msg}{$key} = $innd_no_colon_space{$key};
2349    }
2350  }
2351  if (%innd_huge) {
2352    my $key;
2353    my $msg = 'Huge articles';
2354    foreach $key (keys %innd_huge) {
2355      $innd_misc_stat{$msg}{$key} = $innd_huge{$key};
2356    }
2357  }
2358  if (%innd_blocked) {
2359    my $key;
2360    my $msg = 'Blocked server feeds';
2361    foreach $key (keys %innd_blocked) {
2362      $innd_misc_stat{$msg}{$key} = $innd_blocked{$key};
2363    }
2364  }
2365  if (%innd_strange_strings) {
2366    my $key;
2367    my $msg = 'Including strange strings';
2368    foreach $key (keys %innd_strange_strings) {
2369      $innd_misc_stat{$msg}{$key} = $innd_strange_strings{$key};
2370    }
2371  }
2372  if (%rnews_bogus_ng) {
2373    my $key;
2374    my $msg = 'Unwanted newsgroups';
2375    foreach $key (keys %rnews_bogus_ng) {
2376      $rnews_misc{$msg}{$key} = $rnews_bogus_ng{$key};
2377    }
2378  }
2379  if (%rnews_bogus_dist) {
2380    my $key;
2381    my $msg = 'Unwanted distributions';
2382    foreach $key (keys %rnews_bogus_dist) {
2383      $rnews_misc{$msg}{$key} = $rnews_bogus_dist{$key};
2384    }
2385  }
2386  if (%rnews_unapproved) {
2387    my $key;
2388    my $msg = 'Articles unapproved';
2389    foreach $key (keys %rnews_unapproved) {
2390      $rnews_misc{$msg}{$key} = $rnews_unapproved{$key};
2391    }
2392  }
2393  if (%rnews_bogus_date) {
2394    my $key;
2395    my $msg = 'Bad Date';
2396    foreach $key (keys %rnews_bogus_date) {
2397      $rnews_misc{$msg}{$key} = $rnews_bogus_date{$key};
2398    }
2399  }
2400
2401  $rnews_misc{'Too old'}{'--'} = $rnews_too_old if $rnews_too_old;
2402  $rnews_misc{'Bad linecount'}{'--'} = $rnews_linecount if $rnews_linecount;
2403  $rnews_misc{'Duplicate articles'}{'--'} = $rnews_duplicate
2404    if $rnews_duplicate;
2405  $rnews_misc{'No colon-space'}{'--'} = $rnews_no_colon_space
2406    if $rnews_no_colon_space;
2407
2408  if (%nnrpd_groups) {
2409    foreach my $key (keys (%nnrpd_connect)) {
2410      unless ($nnrpd_groups{$key} || $nnrpd_post_ok{$key} ||
2411              $nnrpd_post_rej{$key} || $nnrpd_post_error{$key} ||
2412              $nnrpd_articles{$key}) {
2413	$nnrpd_curious{$key} = $nnrpd_connect{$key};
2414	delete $nnrpd_connect{$key};
2415      }
2416    }
2417  }
2418}
2419
2420sub report_unwanted_ng($) {
2421  my $file = shift;
2422  open (FILE, "$file") && do {
2423    while (<FILE>) {
2424      my ($c, $n) = $_ =~ m/^\s*(\d+)\s+(.*)$/;
2425      next unless defined $n;
2426      $n =~ s/^newsgroup //o; # for pre 1.8 logs
2427      $inn_uw_ng{$n} += $c;
2428    }
2429    close (FILE);
2430  };
2431
2432  unlink ("${file}.old");
2433  rename ($file, "${file}.old");
2434
2435  open (FILE, "> $file") && do {
2436    my $g;
2437    foreach $g (sort {$inn_uw_ng{$b} <=> $inn_uw_ng{$a}} (keys (%inn_uw_ng))) {
2438      printf FILE "%d %s\n", $inn_uw_ng{$g}, $g;
2439    }
2440    close (FILE);
2441    chmod(0660, "$file");
2442  };
2443  unlink ("${file}.old");
2444}
2445
2446###########################################################################
2447
2448# Compare 2 dates (+hour), used with sort (arguments $a and $b)
2449sub datecmp() {
2450  # ex: "May 12 06"   for May 12, 6:00am
2451  # The 2 dates are near. The range is less than a few days that's why we
2452  # can cheat to determine the order. It is only important if one date
2453  # is in January and the other in December.
2454
2455  my($date1) = substr($a, 4, 2) * 24;
2456  my($date2) = substr($b, 4, 2) * 24;
2457  $date1 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($a,0,3)) * 288;
2458  $date2 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($b,0,3)) * 288;
2459  if ($date1 - $date2 > 300 * 24) {
2460    $date2 += 288 * 3 * 12;
2461  }
2462  elsif ($date2 - $date1 > 300 * 24) {
2463    $date1 += 288 * 3 * 12;
2464  }
2465  $date1 += substr($a, 7, 2);
2466  $date2 += substr($b, 7, 2);
2467  $date1 - $date2;
2468}
2469
2470sub host2dom($) {
2471  my $host = shift;
2472
2473  $host =~ m/^[^\.]+(.*)/;
2474  $host =~ m/^[\d\.]+$/ ? "unresolved" : $1 ? "*$1" : "?";
2475}
2476
24771;
2478