--- /dev/null
+++ b/extra/etc/innreport.conf
@@ -0,0 +1,2458 @@
+##########################################################
+# Configuration file for innreport (3.*).
+#
+# Sample file for INN.
+# Tested with INN 2.3, 2.1, 1.7.2 and 1.5.1.
+#
+# (c) 1997, 1998, 1999 by Fabien Tassin <fta@sofaraway.org>
+# version 3.0.2
+##########################################################
+
+# Default parameters
+section default {
+	libpath         "/usr/lib/news";
+	logpath		"/var/log/news";
+	unknown		true;        # want unknown entries.
+	max_unknown     50;          # max unknown entries to display.
+	casesensitive   true;
+	module		"innreport_inn";  # ${libpath}/${module}.pm
+	unwanted_log	"unwanted.log";   # ${logpath}/${unwanted_log}
+	text            true;
+	html            false;
+	graph           true;        # need 'html'
+	archive		true;  # use false to keep only the latest HTML report.
+	index		"index.html"; # name of the HTML index file.
+      # html_dir     "/var/www/News/stats";
+	img_dir         "pics";      # images will go to ${html_dir}/${img_dir}
+	cycle		30;          # use a number or 'none'.
+        separator       ".";         # use a valid filename character.
+	title		"Daily Usenet report";
+      # title	     "Daily Usenet report for <A HREF=\"/stat/\">news.y.z</A>";
+      # footer		"Local contact: <A HREF=\"mailto:x@y.z\">x@y.z</A>";
+      # html_body	"BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\"";
+      # html_header_file "header.html";  # ${html_dir}/${html_header_file}
+      # html_footer_file "footer.html";  # ${html_dir}/${html_footer_file}
+        graph_width	550;         # graph width (in pixels)
+        transparent	true;        # graph background transparent ?
+        graph_fg        "#000000";   # graph foreground color.
+        graph_bg        "#FFFFFF";   # graph background color.
+};
+
+###########################################################################
+# Index page
+section index {
+	column {
+		title	"Dates";
+		value	"date";
+	};
+	column {
+		title	"Incoming feeds";
+		name	"Offered|Accepted|Volume";
+		value	"total(%innd_offered) | total(%innd_accepted) |
+		         bytes(total(%inn_flow_size))";
+	};
+	column {
+		title	"Outgoing feeds";
+		name	"Offered|Accepted|Volume";
+		value	"total(%innfeed_offered) + total(%nntplink_offered) +
+			 total(%innxmit_offered) | total(%innfeed_accepted) +
+			 total(%nntplink_accepted) + total(%innxmit_accepted) |
+			 bytes(total(%innfeed_accepted_size) +
+			 total(%innfeed_rejected_size) +
+			 total(%innxmit_bytes) +
+			 total(%innxmit_accepted_size) +
+			 total(%innxmit_rejected_size))";
+	};
+	graph {
+	        title         "Incoming feeds";
+		value	      val1;
+		color	      "#FFFFCE";
+		unit	      "art";
+		data {
+		          name    "Offered";
+			  color   "#50A0D0";
+			  value   "val2";       # Incoming feeds: Offered
+		};
+		data {
+		          name    "Accepted";
+			  color   "#0000FF";
+			  value   "val3";       # Incoming feeds: Accepted
+		};
+	};
+	graph {
+	        title         "Outgoing feeds";
+		value	      val1;
+		color	      "#FFFFCE";
+		unit	      "art";
+		data {
+		          name    "Offered";
+			  color   "#50A0D0";
+			  value   "val5";       # Outgoing feeds: Offered
+		};
+		data {
+		          name    "Accepted";
+			  color   "#0000FF";
+			  value   "val6";       # Outgoing feeds: Accepted
+		};
+	};
+	graph {
+		title		"Bandwidth";
+		value		val1;
+		color		"#FFFFCE";
+		unit		"Kb";
+		data {
+			name	"Incoming";
+			color	"#50A0D0";
+			value	"byte(val4)";	# Incoming feeds: Volume
+		};
+		data {
+			name	"Outgoing";
+			color	"#0000FF";
+			value	"byte(val7)";	# Outgoing feeds: Volume
+		};
+	};
+};
+
+###########################################################################
+# Report
+
+section prog_type {
+        # skip    true;                          # used to skip a section.
+        title   "Log entries by program:";
+	data    "%prog_type";
+	sort    "$prog_type{$b} <=> $prog_type{$a}";
+	# text	false;             # to skip this section in the text report
+	# html	false;             # to skip this section in the HTML report
+        column {
+                name          "Program name";
+                format        "%-46.46s";
+		value         "$key";
+		format_total  "TOTAL: %-39.39s";
+		total         "$num";
+        };
+        column {
+                name          "Lines";
+                format_name   "%7s";
+                format        "%7d";
+	        # text	      false;  # to skip this column in the text report
+		value         "$prog_type{$key}";
+		total         "total(%prog_type)";
+        };
+        column {
+                name          "%Lines";
+                format_name   "%7s";
+                format        "%6.1f%%";
+	        # html	      false;  # to skip this column in the HTML report
+		value         "$prog_type{$key} / total(%prog_type) * 100";
+		total         "100";
+        };
+        column {
+                name          "Size";
+                format        "%9s";
+		value         "bytes($prog_size{$key})";
+		total         "bytes(total(%prog_size))";
+        };
+        column {
+                name          "%Size";
+                format_name   "%6s";
+                format        "%5.1f%%";
+		value         "$prog_size{$key} / total(%prog_size) * 100";
+		total         "100";
+        };
+};
+
+# INN 2.*
+section innd_his {
+	title	"History cache:";
+	data	"%innd_his";
+	sort    "$innd_his{$b} <=> $innd_his{$a}";
+	column {
+		name	      "Reason";
+	        format        "%-57.57s";
+		value         "$key";
+		format_total  "TOTAL: %-50.50s";
+		total         "$num";
+	};
+	column {
+		name          "Count";
+		format_name   "%10s";
+                format        "%10d";
+		value         "$innd_his{$key}";
+		total         "total(%innd_his)";
+        };
+	column {
+		name          "%Count";
+		format_name   "%10s";
+                format        "%9.1f%%";
+		value         "100 * $innd_his{$key} / total(%innd_his)";
+		total         "100";
+        };
+};
+
+# INN 1.*
+section innd_cache {
+	title	"History cache:";
+	data	"%innd_cache";
+	sort    "$innd_cache{$b} <=> $innd_cache{$a}";
+	column {
+		name	      "Reason";
+	        format        "%-57.57s";
+		value         "$key";
+		format_total  "TOTAL: %-50.50s";
+		total         "$num";
+	};
+	column {
+		name          "Count";
+		format_name   "%10s";
+                format        "%10d";
+		value         "$innd_cache{$key}";
+		total         "total(%innd_cache)";
+        };
+	column {
+		name          "%Count";
+		format_name   "%10s";
+                format        "%9.1f%%";
+		value         "100 * $innd_cache{$key} / total(%innd_cache)";
+		total         "100";
+        };
+};
+
+section innd_timer {
+        title   "INND timer:";
+	data    "%innd_time_time";
+        column {
+                name          "Code region";
+                format        "%-15.15s";
+		value         "$key";
+		format_total  "TOTAL: %-8.8s";
+		total         "time_ms($innd_time_times)";
+        };
+        column {
+                name          "Time";
+                format        "%13s";
+		value         "time_ms($innd_time_time{$key})";
+		total         "time_ms(total(%innd_time_time))";
+        };
+        column {
+                name          "Pct";
+                format_name   "%6s";
+                format        "%5.1f%%";
+		value         "100 * $innd_time_time{$key} / $innd_time_times";
+		total         "100 * total(%innd_time_time) /
+	                       $innd_time_times";
+        };
+        column {
+                name          "Invoked";
+                format        "%10s";
+		value         "$innd_time_num{$key}";
+                format_total  "%9s-";
+		total         "";
+        };
+        column {
+                name          "Min(ms)";
+                format_name   "%9s";
+                format        "%9.3f";
+		value         "$innd_time_min{$key}";
+                format_total  "%8s-";
+		total         "";
+        };
+        column {
+                name          "Avg(ms)";
+                format_name   "%10s";
+                format        "%10.3f";
+		value         "$innd_time_time{$key} /
+		            ($innd_time_num{$key} ? $innd_time_num{$key} : 1)";
+                format_total  "%9s-";
+		total         "";
+        };
+        column {
+                name          "Max(ms)";
+                format_name   "%10s";
+                format        "%10.3f";
+		value         "$innd_time_max{$key}";
+                format_total  "%9s-";
+		total         "";
+        };
+};
+
+section innfeed_timer {
+        title   "INNfeed timer:";
+	data    "%innfeed_time_time";
+        column {
+                name          "Code region";
+                format        "%-15.15s";
+		value         "$key";
+		format_total  "TOTAL: %-8.8s";
+		total         "time_ms($innfeed_time_times)";
+        };
+        column {
+                name          "Time";
+                format        "%13s";
+		value         "time_ms($innfeed_time_time{$key})";
+		total         "time_ms(total(%innfeed_time_time))";
+        };
+        column {
+                name          "Pct";
+                format_name   "%6s";
+                format        "%5.1f%%";
+		value         "100 * $innfeed_time_time{$key} / $innfeed_time_times";
+		total         "100 * total(%innfeed_time_time) /
+	                       $innfeed_time_times";
+        };
+        column {
+                name          "Invoked";
+                format        "%10s";
+		value         "$innfeed_time_num{$key}";
+                format_total  "%9s-";
+		total         "";
+        };
+        column {
+                name          "Min(ms)";
+                format_name   "%9s";
+                format        "%9.3f";
+		value         "$innfeed_time_min{$key}";
+                format_total  "%8s-";
+		total         "";
+        };
+        column {
+                name          "Avg(ms)";
+                format_name   "%10s";
+                format        "%10.3f";
+		value         "$innfeed_time_time{$key} /
+		            ($innfeed_time_num{$key} ? $innfeed_time_num{$key} : 1)";
+                format_total  "%9s-";
+		total         "";
+        };
+        column {
+                name          "Max(ms)";
+                format_name   "%10s";
+                format        "%10.3f";
+		value         "$innfeed_time_max{$key}";
+                format_total  "%9s-";
+		total         "";
+        };
+};
+
+section nnrpd_timer {
+        title   "nnrpd timer:";
+	data    "%nnrpd_time_time";
+        column {
+                name          "Code region";
+                format        "%-15.15s";
+		value         "$key";
+		format_total  "TOTAL: %-8.8s";
+		total         "time_ms($nnrpd_time_times)";
+        };
+        column {
+                name          "Time";
+                format        "%13s";
+		value         "time_ms($nnrpd_time_time{$key})";
+		total         "time_ms(total(%nnrpd_time_time))";
+        };
+        column {
+                name          "Pct";
+                format_name   "%6s";
+                format        "%5.1f%%";
+		value         "100 * $nnrpd_time_time{$key} / $nnrpd_time_times";
+		total         "100 * total(%nnrpd_time_time) /
+	                       $nnrpd_time_times";
+        };
+        column {
+                name          "Invoked";
+                format        "%10s";
+		value         "$nnrpd_time_num{$key}";
+                format_total  "%9s-";
+		total         "";
+        };
+        column {
+                name          "Min(ms)";
+                format_name   "%9s";
+                format        "%9.3f";
+		value         "$nnrpd_time_min{$key}";
+                format_total  "%8s-";
+		total         "";
+        };
+        column {
+                name          "Avg(ms)";
+                format_name   "%10s";
+                format        "%10.3f";
+		value         "$nnrpd_time_time{$key} /
+		            ($nnrpd_time_num{$key} ? $nnrpd_time_num{$key} : 1)";
+                format_total  "%9s-";
+		total         "";
+        };
+        column {
+                name          "Max(ms)";
+                format_name   "%10s";
+                format        "%10.3f";
+		value         "$nnrpd_time_max{$key}";
+                format_total  "%9s-";
+		total         "";
+        };
+};
+
+section innd_control {
+        title   "Control commands to INND:";
+	data    "%innd_control";
+        column {
+                name          "Command";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Number";
+                format_name   "%7s";
+		value         "$innd_control{$key}";
+		format        "%7d";
+		total         "total(%innd_control)";
+        };
+};
+
+section innd_newgroup {
+        title   "Newsgroups created:";
+	data    "%innd_newgroup";
+        column {
+                name          "Group";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL%-66.66s";
+		total         "";
+        };
+        column {
+                name          "Mode";
+		value         "$innd_newgroup{$key}";
+		format        "%7s";
+		total         "$num";
+        };
+};
+
+section innd_changegroup {
+	title	"Newsgroups changed:";
+	data	"%innd_changegroup";
+	column {
+		name          "Group";
+		format        "%-68.68s";
+		value         "$key";
+		format_total  "TOTAL%-63.63s";
+		total         "";
+	};
+	column {
+		name          "New mode";
+		format        "%10s";
+		value         "$innd_changegroup{$key}";
+		total         "$num";
+	};
+};
+
+section innd_rmgroup {
+	title	"Newsgroups removed:";
+	data	"%innd_rmgroup";
+	column {
+		name          "Group";
+		format        "%-78.78s";
+		value         "$key";
+		format_total  "TOTAL: %-71.71s";
+		total         "$num";
+	};
+};
+
+section controlchan {
+	title   "Control Channel:";
+	data    "%controlchan_who";
+	column {
+		name          "Sender";
+		format        "%-25.25s";
+		value         "$key";
+		format_total  "TOTAL%-20.20s";
+		total         "";
+	};
+	column {
+		name          "newgroup";
+		value         "$controlchan_new{$key}";
+		format        "%8s";
+		total         "total(%controlchan_new)";
+	};
+	column {
+		name          "rmgroup";
+		value         "$controlchan_rm{$key}";
+		format        "%8s";
+		total         "total(%controlchan_rm)";
+	};
+	column {
+		name          "Other";
+		value         "$controlchan_other{$key}";
+		format        "%8s";
+		total         "total(%controlchan_other)";
+	};
+	column {
+		name          "Bad PGP";
+		value         "$controlchan_skippgp{$key}";
+		format        "%8s";
+		total         "total(%controlchan_skippgp)";
+	};
+	column {
+		name          "DoIt";
+		value         "$controlchan_doit{$key}";
+		format        "%8s";
+		total         "total(%controlchan_doit)";
+	};
+	column {
+		name          "OK";
+		value         "$controlchan_ok{$key}";
+		format        "%8s";
+		total         "total(%controlchan_ok)";
+	};
+};
+
+section innd_connect {
+        title   "Incoming Feeds (INN):";
+	data    "%innd_seconds";
+	sort    "$innd_accepted{$b} <=> $innd_accepted{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format_name   "%-21.21s";
+		format        "%-24.24s";
+		value         "$key";
+		format_total  "TOTAL: %-17.17s";
+		total         "$num";
+        };
+        column {
+                name          "Connects";
+		format_name   "%5s";
+		format        "%5d";
+		value         "$innd_connect{$key}";
+		total         "total(%innd_connect)";
+        };
+        column {
+                name          "Offered";
+		format_name   "%8s";
+		format        "%8d";
+		value         "$innd_offered{$key}";
+		total         "total(%innd_offered)";
+        };
+        column {
+                name          "Taken";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innd_accepted{$key}";
+		total         "total(%innd_accepted)";
+        };
+        column {
+                name          "Refused";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innd_refused{$key}";
+		total         "total(%innd_refused)";
+        };
+        column {
+                name          "Reject";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innd_rejected{$key}";
+		total         "total(%innd_rejected)";
+        };
+        column {
+                name          "%Accpt";
+		format_name   "%6s";
+		format        "%4d%%";
+		value         "$innd_offered{$key} == 0 ? 0 :
+                             $innd_accepted{$key} / $innd_offered{$key} * 100";
+		total         "total(%innd_offered) == 0 ? 0 :
+                           total(%innd_accepted) / total(%innd_offered) * 100";
+        };
+        column {
+                name          "Elapsed";
+		format_name   "%8s";
+		format        "%9s";
+		value         "time($innd_seconds{$key})";
+		total         "time(total(%innd_seconds))";
+        };
+	graph {
+	        title         "Articles received by server";
+		type          histo3d;
+		sort	      "%innd_accepted";
+		data {
+		          name    "Articles accepted";
+			  color   "#0000FF";
+			  value   "%innd_accepted";
+                };
+		data {
+		          name    "Articles refused";
+			  color   "#FFAF00";
+			  value   "%innd_refused";
+                };
+		data {
+		          name    "Articles rejected";
+			  color   "#FF0000";
+			  value   "%innd_rejected";
+                };
+	};
+};
+
+section innd_incoming_vol {
+        title   "Incoming Volume (INN):";
+	data    "%innd_seconds";
+	sort    "$innd_stored_size{$b} <=> $innd_stored_size{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format        "%-24.24s";
+		value         "$key";
+		format_total  "TOTAL: %-17.17s";
+		total         "$num";
+        };
+        column {
+                name          "AcceptVol";
+		format        "%9s";
+		value         "bytes($innd_stored_size{$key})";
+		total         "bytes(total(%innd_stored_size))";
+        };
+        column {
+                name          "DupVol";
+		format        "%9s";
+		value         "bytes($innd_duplicated_size{$key})";
+		total         "bytes(total(%innd_duplicated_size))";
+        };
+	column {
+		name	      "TotalVol";
+		format	      "%9s";
+		value	      "bytes($innd_stored_size{$key} +
+	                               $innd_duplicated_size{$key})";
+		total	      "bytes(total(%innd_stored_size) +
+                                       total(%innd_duplicated_size))";
+	};
+        column {
+                name          "%Acc";
+		format_name   "%4s";
+		format        "%3d%%";
+		value         "$innd_offered_size{$key} == 0 ? 0 :
+		     $innd_stored_size{$key} / $innd_offered_size{$key} * 100";
+		total         "total(%innd_offered_size) == 0 ? 0 :
+		   total(%innd_stored_size) / total(%innd_offered_size) * 100";
+        };
+	column {
+		name		"Vol/Art";
+		format		"%9s";
+		value		"bytes(($innd_stored_size{$key} +
+                                        $innd_duplicated_size{$key}) /
+                                       ($innd_accepted{$key} +
+                                        $innd_rejected{$key}))";
+		total		"bytes((total(%innd_stored_size) +
+                                        total(%innd_duplicated_size)) /
+                                     (total(%innd_accepted) +
+	                              total(%innd_rejected)))";
+	};
+        column {
+                name          "Elapsed";
+		format        "%9s";
+		value         "time($innd_seconds{$key})";
+		total         "time(total(%innd_seconds))";
+        };
+	graph {
+	        title         "Incoming Volume received by server";
+		type          histo3d;
+		sort	      "%innd_stored_size";
+		data {
+		          name    "Accepted Volume";
+			  color   "#0000FF";
+			  value   "%innd_stored_size";
+                };
+		data {
+		          name    "Duplicated Volume";
+			  color   "#FFAF00";
+			  value   "%innd_duplicated_size";
+                };
+	};
+};
+
+section inn_flow {
+        title   "Incoming articles:";
+	data    "%inn_flow";
+	sort    "&DateCompare";
+        column {
+                name          "Date";
+                format        "%-27.27s";
+		value         "$key";
+		format_total  "TOTAL: %-20.20s";
+		total         "time(total(%inn_flow_time))";
+        };
+        column {
+                name          "Articles";
+		format_name   "%8s";
+		value         "$inn_flow{$key}";
+		format        "%8d";
+		total         "total(%inn_flow)";
+        };
+        column {
+                name          "%Arts";
+		format_name   "%8s";
+		value         "$inn_flow{$key} / $inn_flow_total * 100";
+		format        "%7.1f%%";
+		total         "100";
+        };
+        column {
+                name          "Art/sec";
+		format_name   "%7s";
+		value         "$inn_flow{$key} / $inn_flow_time{$key}";
+		format        "%7.2f";
+		total         "total(%inn_flow) / total(%inn_flow_time)";
+        };
+        column {
+                name          "Size";
+		value         "bytes($inn_flow_size{$key})";
+		format        "%9s";
+		total         "bytes(total(%inn_flow_size))";
+        };
+        column {
+                name          "%Size";
+		format_name   "%7s";
+		value         "$inn_flow_size{$key} /
+                                          total(%inn_flow_size) * 100";
+		format        "%6.1f%%";
+		total         "100";
+        };
+        column {
+                name          "KB/sec";
+		format_name   "%7s";
+		value         "$inn_flow_size{$key} /
+		                       $inn_flow_time{$key} / 1024";
+		format        "%7.2f";
+		total         "total(%inn_flow_size) /
+                                       total(%inn_flow_time) / 1024";
+        };
+	graph {
+	        title         "Incoming articles";
+		type          histo;
+		data {
+		          name    "Hours";
+			  value   "%inn_flow_labels";
+                };
+		data {
+		          name    "Art/sec";
+			  factor  3600;
+			  value   "%inn_flow";
+                };
+        };
+	graph {
+	        title         "Incoming articles (size)";
+		type          histo;
+		data {
+		          name    "Hours";
+			  value   "%inn_flow_labels";
+                };
+		data {
+		          name    "Kb/sec";
+			  factor  3686400;   # 3600 * 1024
+			  value   "%inn_flow_size";
+                };
+        };
+};
+
+section cnfsstat {
+        title   "CNFS buffer status:";
+        data    "%cnfsstat";
+        column {
+                name          "Buffer";
+                format        "%-13.13s";
+                value         "$key";
+                format_total  "TOTAL: %-6.6s";
+                total         "$num";
+        };
+        column {
+                name          "Class";
+                format        "%-13.13s";
+                value         "$cnfsstat{$key}";
+                format_total  "-%12s";
+                total         "";
+        };
+        column {
+                name          "Size";
+                format        "%9s";
+                value         "bytes($cnfsstat_size{$key})";
+                total         "bytes(total(%cnfsstat_size))";
+        };
+        column {
+                name          "Used";
+                format        "%9s";
+                value         "bytes($cnfsstat_used{$key})";
+                total         "bytes(total(%cnfsstat_used))";
+        };
+        column {
+                name          "%Used";
+                format_name   "%7s";
+                value         "$cnfsstat_used{$key} /
+                                       $cnfsstat_size{$key} * 100";
+                format        "%6.1f%%";
+                total         "total(%cnfsstat_used) /
+                                       total(%cnfsstat_size) * 100";
+        };
+        column {
+                name          "Cycles";
+                format_name   "%6s";
+                format        "%6d";
+                value         "$cnfsstat_cycles{$key}";
+                total         "total(%cnfsstat_cycles)";
+        };
+        column {
+                name          "KB/sec";
+                format_name   "%7s";
+                value         "$cnfsstat_rate{$key} /
+                                       $cnfsstat_samples{$key} / 1024";
+                format        "%7.2f";
+                total         "total(%cnfsstat_rate) /
+                                       total(%cnfsstat_samples) / 1024";
+        };
+        column {
+                name          "Days";
+                format_name   "%8s";
+                value         "$cnfsstat_size{$key} /
+                                 ($cnfsstat_rate{$key} /
+                                    $cnfsstat_samples{$key}) / 86400";
+                format        "%8.2f";
+                format_total  "%7s-";
+                total         "";
+        };
+};
+
+section inn_unwanted {
+        title   "Sites sending bad articles:";
+	data    "%inn_badart";
+	sort    "$inn_badart{$b} <=> $inn_badart{$a}";
+	numbering true;
+        column {
+                name          "Server";
+                format        "%-23.23s";
+		value         "$key";
+		format_total  "TOTAL: %-16.16s";
+		total         "$num";
+        };
+        column {
+                name          "Total";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$inn_badart{$key}";
+		total         "total(%inn_badart)";
+        };
+        column {
+                name          "Group";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$inn_uw_ng_s{$key}";
+		total         "total(%inn_uw_ng_s)";
+        };
+        column {
+                name          "Dist";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$inn_uw_dist_s{$key}";
+		total         "total(%inn_uw_dist_s)";
+        };
+        column {
+                name          "Duplic";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$inn_duplicate{$key}";
+		total         "total(%inn_duplicate)";
+        };
+        column {
+                name          "Unapp";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$inn_unapproved{$key}";
+		total         "total(%inn_unapproved)";
+        };
+        column {
+                name          "TooOld";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$inn_tooold{$key}";
+		total         "total(%inn_tooold)";
+        };
+        column {
+                name          "Site";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$inn_uw_site{$key}";
+		total         "total(%inn_uw_site)";
+        };
+        column {
+                name          "Line";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$inn_linecount{$key}";
+		total         "total(%inn_linecount)";
+        };
+        column {
+                name          "Other";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$innd_others{$key}";
+		total         "total(%innd_others)";
+        };
+};
+
+section inn_unwanted_group {
+        title   "Unwanted newsgroups:";
+	top     20; # default 'top' value or use 'top_text' and 'top_html'
+                    # to specify different values for text and HTML reports.
+	data    "%inn_uw_ng";
+	sort    "$inn_uw_ng{$b} <=> $inn_uw_ng{$a}";
+        column {
+                name          "Newsgroup";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$inn_uw_ng{$key}";
+		total         "total(%inn_uw_ng)";
+        };
+};
+
+section inn_unwanted_dist {
+        title   "Unwanted distributions:";
+	top     20;
+	data    "%inn_uw_dist";
+	sort    "$inn_uw_dist{$b} <=> $inn_uw_dist{$a}";
+        column {
+                name          "Distribution";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$inn_uw_dist{$key}";
+		total         "total(%inn_uw_dist)";
+        };
+};
+
+section inn_unwanted_unapp {
+        title   "Supposedly-moderated groups with unmoderated postings:";
+	top     20;
+	data    "%inn_unapproved_g";
+	sort    "$inn_unapproved_g{$b} <=> $inn_unapproved_g{$a}";
+        column {
+                name          "Groups";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$inn_unapproved_g{$key}";
+		total         "total(%inn_unapproved_g)";
+        };
+};
+
+section inn_unwanted_path {
+        title   "Unwanted sites in Path:";
+	top     20;
+	data    "%inn_site_path";
+	sort    "$inn_site_path{$b} <=> $inn_site_path{$a}";
+        column {
+                name          "Site";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$inn_site_path{$key}";
+		total         "total(%inn_site_path)";
+        };
+};
+
+section innd_perl {
+        title   "INND perl filter:";
+	top     20;
+	data    "%innd_filter_perl";
+	sort    "$innd_filter_perl{$b} <=> $innd_filter_perl{$a}";
+        column {
+                name          "Reason";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$innd_filter_perl{$key}";
+		total         "total(%innd_filter_perl)";
+        };
+};
+
+section nocem {
+        title   "NoCeM on Spool:";
+	data    "%nocem_goodsigs";
+	sort    "$nocem_ids{$b} <=> $nocem_ids{$a}";
+	column {
+		name		"Id";
+               	format		"%-47.47s";
+                value         	"$key";
+		format_total  	"TOTAL: %-40.40s";
+                total         	"$num";
+	};
+	column {
+		name		"Good";
+		format		"%7s";
+		value		"$nocem_goodsigs{$key}";
+                total         	"total(%nocem_goodsigs)";
+	};
+	column {
+		name		"Bad";
+		format		"%7s";
+                value		"$nocem_badsigs{$key}";
+		total		"total(%nocem_badsigs)";
+	};
+	column {
+		name		"Unique";
+		format		"%7s";
+		value		"$nocem_newids{$key}";
+		total		"total(%nocem_newids)";
+	};
+	column {
+		name		"Total";
+		format		"%7s";
+		value		"$nocem_totalids{$key}";
+		total		"total(%nocem_totalids)";
+	};
+};
+
+section innd_no_permission {
+	title	"INND no permission servers:";
+	data	"%innd_no_permission";
+	sort	"$innd_no_permission{$b} <=> $innd_no_permission{$a}";
+	column {
+		name		"System";
+		format		"%-71.71s";
+		value		"$key";
+		format_total	"TOTAL: %-64.64s";
+		total		"$num";
+	};
+	column {
+		name		"Conn";
+		format_name	"%7s";
+		format		"%7d";
+		value		"$innd_no_permission{$key}";
+		total		"total(%innd_no_permission)";
+	};
+};
+
+section innd_max_conn {
+        title   "Too many incoming connections (innd):";
+	data    "%innd_max_conn";
+	sort    "$innd_max_conn{$b} <=> $innd_max_conn{$a}";
+        column {
+                name          "Server";
+                format        "%-70.70s";
+		value         "$key";
+		format_total  "TOTAL: %-63.63s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+		format_name   "%8s";
+                format        "%8d";
+		value         "$innd_max_conn{$key}";
+		total         "total(%innd_max_conn)";
+        };
+};
+
+section innd_too_many_connects_per_minute {
+	title	"INND too many connects per minute:";
+	data	"%innd_too_many_connects_per_minute";
+	sort	"$innd_too_many_connects_per_minute{$b} <=>
+                 $innd_too_many_connects_per_minute{$a}";
+	column {
+		name		"System";
+		format		"%-71.71s";
+		value		"$key";
+		format_total	"TOTAL: %-64.64s";
+		total		"$num";
+	};
+	column {
+		name		"Conn";
+		format_name	"%7s";
+		format		"%7d";
+		value		"$innd_too_many_connects_per_minute{$key}";
+		total		"total(%innd_too_many_connects_per_minute)";
+	};
+};
+
+section innd_misc {
+        title   "INND misc events:";
+	data    "%innd_misc";
+	sort    "$innd_misc{$b} <=> $innd_misc{$a}";
+        column {
+                name          "Events";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$innd_misc{$key}";
+		total         "total(%innd_misc)";
+        };
+};
+
+section innd_misc_stat {
+        title   "Miscellaneous innd statistics:";
+	data    "%innd_misc_stat";
+	sort    "$innd_misc_stat{$b} <=> $innd_misc_stat{$a}";
+	double  true;
+	top	10;
+	#numbering true;
+        column {
+	        primary       true;
+                name          "Event";
+		format        "%-69.69s";
+		value         "$key1";
+		format_total  "TOTAL: %-62.62s";
+		total         "$num";
+        };
+        column {
+                name          "Server";
+		format        "  %-67.67s";
+		value         "$key2";
+		total         "$num";
+		format_total  "TOTAL: %-60.60s";
+        };
+        column {
+                name          "Number";
+		format_name   "%9s";
+		format        "%9d";
+		value         "$innd_misc_stat{$key1}{$key2}";
+		total         "total(%innd_misc_stat)";
+        };
+};
+
+section innfeed_connect {
+	title	"Outgoing Feeds (innfeed) by Articles:";
+	data	"%innfeed_offered";
+	sort	"$innfeed_accepted{$b} <=> $innfeed_accepted{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format        "%-18.18s";
+		value         "$key";
+		format_total  "TOTAL: %-11.11s";
+		total         "$num";
+        };
+        column {
+                name          "Offered";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innfeed_offered{$key}";
+		total         "total(%innfeed_offered)";
+        };
+        column {
+                name          "Taken";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innfeed_accepted{$key}";
+		total         "total(%innfeed_accepted)";
+        };
+        column {
+                name          "Refused";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innfeed_refused{$key}";
+		total         "total(%innfeed_refused)";
+        };
+        column {
+                name          "Reject";
+		format_name   "%6s";
+		format        "%6d";
+		value         "$innfeed_rejected{$key}";
+		total         "total(%innfeed_rejected)";
+        };
+        column {
+                name          "Miss";
+		format_name   "%6s";
+		format        "%6d";
+		value         "$innfeed_missing{$key}";
+		total         "total(%innfeed_missing)";
+        };
+        column {
+                name          "Spool";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innfeed_spooled{$key}";
+		total         "total(%innfeed_spooled)";
+        };
+        column {
+                name          "%Took";
+		format_name   "%5s";
+		format        "%3d%%";
+		value         "$innfeed_offered{$key} == 0 ? 0 :
+		       $innfeed_accepted{$key} / $innfeed_offered{$key} * 100";
+		total         "total(%innfeed_offered) == 0 ? 0 :
+		     total(%innfeed_accepted) / total(%innfeed_offered) * 100";
+        };
+        column {
+                name          "Elapsed";
+		format_name   "%8s";
+		format        "%9s";
+		value         "time($innfeed_seconds{$key})";
+		total         "time(total(%innfeed_seconds))";
+        };
+	graph {
+	        title         "Outgoing feeds (innfeed) by Articles";
+		type          histo3d;
+		sort	      "%innfeed_accepted";
+		data {
+		          name    "Accepted";
+			  color   "#0000FF";
+			  value   "%innfeed_accepted";
+                };
+		data {
+		          name    "Refused";
+			  color   "#FFAF00";
+			  value   "%innfeed_refused";
+                };
+		data {
+		          name    "Rejected";
+			  color   "#FF0000";
+			  value   "%innfeed_rejected";
+                };
+		data {
+		          name    "Missing";
+			  color   "#00FF00";
+			  value   "%innfeed_missing";
+                };
+		data {
+		          name    "Spooled";
+			  color   "#AF00FF";
+			  value   "%innfeed_spooled,";
+                };
+	};
+};
+
+section innfeed_volume {
+	title	"Outgoing Feeds (innfeed) by Volume:";
+	data	"%innfeed_offered";
+	sort	"$innfeed_accepted_size{$b} <=> $innfeed_accepted_size{$a}";
+	numbering true;
+	column {
+		name		"Server";
+		format		"%-17.17s";
+		value		"$key";
+		format_total	"TOTAL: %-10.10s";
+		total		"$num";
+	};
+	column {
+		name		"AcceptVol";
+		format		"%9s";
+		value		"bytes($innfeed_accepted_size{$key})";
+		total		"bytes(total(%innfeed_accepted_size))";
+	};
+	column {
+		name		"RejectVol";
+		format		"%9s";
+		value		"bytes($innfeed_rejected_size{$key})";
+		total		"bytes(total(%innfeed_rejected_size))";
+	};
+	column {
+		name		"TotalVol";
+		format		"%9s";
+		value		"bytes($innfeed_accepted_size{$key} +
+	                               $innfeed_rejected_size{$key})";
+		total		"bytes(total(%innfeed_accepted_size) +
+                                       total(%innfeed_rejected_size))";
+	};
+	column {
+		name		"Volume/sec";
+		format_name	"%11s";
+		format		"%9s/s";
+		value		"bytes(($innfeed_accepted_size{$key} +
+                                        $innfeed_rejected_size{$key}) /
+                                    $innfeed_seconds{$key})";
+		total		"bytes((total(%innfeed_accepted_size) +
+                                        total(%innfeed_rejected_size)) /
+                                    total(%innfeed_seconds))";
+	};
+	column {
+		name		"Vol/Art";
+		format		"%9s";
+		value		"bytes(($innfeed_accepted_size{$key} +
+                                        $innfeed_rejected_size{$key}) /
+                                       ($innfeed_accepted{$key} +
+                                        $innfeed_rejected{$key}))";
+		total		"bytes((total(%innfeed_accepted_size) +
+                                        total(%innfeed_rejected_size)) /
+                                     (total(%innfeed_accepted) +
+	                              total(%innfeed_rejected)))";
+	};
+	column {
+		name		"Elapsed";
+		format		"%9s";
+		value		"time($innfeed_seconds{$key})";
+		total		"time(total(%innfeed_seconds))";
+	};
+	graph {
+		title		"Outgoing feeds (innfeed) by Volume";
+		type		histo3d;
+		sort		"%innfeed_accepted_size";
+		data {
+			name	"Accepted";
+			color	"#0000FF";
+			value	"%innfeed_accepted_size";
+		};
+		data {
+			name	"Rejected";
+			color	"#FFAF00";
+			value	"%innfeed_rejected_size";
+		};
+		data {
+			name	"Total";
+			color	"#00FF00";
+			value	"%innfeed_accepted_size +
+                                 %innfeed_rejected_size";
+		};
+	};
+};
+
+section innfeed_shrunk {
+        title   "Backlog files shrunk by innfeed:";
+	data    "%innfeed_shrunk";
+	sort    "$innfeed_shrunk{$b} <=> $innfeed_shrunk{$a}";
+        column {
+                name          "Server";
+                format        "%-70.70s";
+		value         "$key";
+		format_total  "TOTAL: %-63.63s";
+		total         "$num";
+        };
+        column {
+                name          "Size";
+                format        "%8s";
+		value         "bytes($innfeed_shrunk{$key})";
+		total         "bytes(total(%innfeed_shrunk))";
+        };
+};
+
+section nntplink_connect {
+        title   "Outgoing Feeds (nntplink):";
+	data    "%nntplink_site";
+	sort    "$nntplink_accepted{$b} <=> $nntplink_accepted{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format        "%-25.25s";
+		value         "$key";
+		format_total  "TOTAL: %-18.18s";
+		total         "$num";
+        };
+        column {
+                name          "Offered";
+		format_name   "%8s";
+		format        "%8d";
+		value         "$nntplink_offered{$key}";
+		total         "total(%nntplink_offered)";
+        };
+        column {
+                name          "Taken";
+		format_name   "%8s";
+		format        "%8d";
+		value         "$nntplink_accepted{$key}";
+		total         "total(%nntplink_accepted)";
+        };
+        column {
+                name          "Rejected";
+		format_name   "%8s";
+		format        "%8d";
+		value         "$nntplink_rejected{$key}";
+		total         "total(%nntplink_rejected)";
+        };
+        column {
+                name          "Failed";
+		format_name   "%8s";
+		format        "%8d";
+		value         "$nntplink_failed{$key}";
+		total         "total(%nntplink_failed)";
+        };
+        column {
+                name          "%Accpt";
+		format_name   "%6s";
+		format        "%5d%%";
+		value         "$nntplink_offered{$key} == 0 ? 0 :
+		     $nntplink_accepted{$key} / $nntplink_offered{$key} * 100";
+		total         "total(%nntplink_offered) == 0 ? 0 :
+		   total(%nntplink_accepted) / total(%nntplink_offered) * 100";
+        };
+        column {
+                name          "Elapsed";
+		format        "%10s";
+		value         "time($nntplink_times{$key})";
+		total         "time(total(%nntplink_times))";
+        };
+	graph {
+	        title         "Outgoing Feeds (nntplink)";
+		type          histo3d;
+		sort	      "%nntplink_accepted";
+		data {
+		          name    "Articles accepted";
+			  color   "#0000FF";
+			  value   "%nntplink_accepted";
+                };
+		data {
+		          name    "Articles rejected";
+			  color   "#FFAF00";
+			  value   "%nntplink_rejected";
+                };
+		data {
+		          name    "Articles failed";
+			  color   "#FF0000";
+			  value   "%nntplink_failed";
+                };
+	};
+};
+
+section nntplink_connect2 {
+        title   "Outgoing Feeds (nntplink) - other information:";
+	data    "%nntplink_site";
+	sort    "$nntplink_accepted{$b} <=> $nntplink_accepted{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format        "%-20.20s";
+		value         "$key";
+		format_total  "TOTAL: %-13.13s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_site{$key}";
+		total         "total(%nntplink_site)";
+        };
+        column {
+                name          "Ok";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_site{$key} - ($nntplink_eof{$key} +
+		               $nntplink_sockerr{$key} +
+			       $nntplink_selecterr{$key} +
+			       $nntplink_hiload{$key} + $nntplink_bpipe{$key} +
+			       $nntplink_nospace{$key} + $nntplink_auth{$key} +
+			       $nntplink_expire{$key} + $nntplink_fail{$key})";
+		total         "total(%nntplink_site) - (total(%nntplink_eof) +
+		               total(%nntplink_sockerr) +
+			       total(%nntplink_selecterr) +
+			       total(%nntplink_hiload) +
+			       total(%nntplink_bpipe) +
+			       total(%nntplink_nospace) +
+			       total(%nntplink_auth) +
+			       total(%nntplink_expire) +
+			       total(%nntplink_fail))";
+        };
+        column {
+                name          "EOF";
+		format_name   "%3s";
+		format        "%3d";
+		value         "$nntplink_eof{$key}";
+		total         "total(%nntplink_eof)";
+        };
+        column {
+                name          "Sock";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_sockerr{$key}";
+		total         "total(%nntplink_sockerr)";
+        };
+        column {
+                name          "Slct";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_selecterr{$key}";
+		total         "total(%nntplink_selecterr)";
+        };
+        column {
+                name          "Load";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_hiload{$key}";
+		total         "total(%nntplink_hiload)";
+        };
+        column {
+                name          "Bpip";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_bpipe{$key}";
+		total         "total(%nntplink_bpipe)";
+        };
+        column {
+                name          "Spce";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_nospace{$key}";
+		total         "total(%nntplink_nospace)";
+        };
+        column {
+                name          "Exp";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_expire{$key}";
+		total         "total(%nntplink_expire)";
+        };
+        column {
+                name          "Auth";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_auth{$key}";
+		total         "total(%nntplink_auth)";
+        };
+        column {
+                name          "Othr";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$nntplink_fail{$key}";
+		total         "total(%nntplink_fail)";
+        };
+        column {
+                name          "Pct";
+		format_name   "%4s";
+		format        "%3d%%";
+		value         "$nntplink_site{$key} ?
+		               100 * ($nntplink_site{$key} -
+			       ($nntplink_eof{$key} + $nntplink_sockerr{$key} +
+			       $nntplink_selecterr{$key} +
+			       $nntplink_hiload{$key} + $nntplink_bpipe{$key} +
+			       $nntplink_nospace{$key} + $nntplink_auth{$key} +
+			       $nntplink_expire{$key} +
+			    $nntplink_fail{$key})) / $nntplink_site{$key} : 0";
+		total         "total(%nntplink_site) ?
+		               100 * (total(%nntplink_site) -
+			       (total(%nntplink_eof) +
+			       total(%nntplink_sockerr) +
+			       total(%nntplink_selecterr) +
+			       total(%nntplink_hiload) +
+			       total(%nntplink_bpipe) +
+			       total(%nntplink_nospace) +
+			       total(%nntplink_auth) +
+			       total(%nntplink_expire) +
+			  total(%nntplink_fail))) / total(%nntplink_site) : 0";
+        };
+};
+
+section innxmit_connect {
+	title	"Outgoing Feeds (innxmit) by Articles:";
+	data    "%innxmit_times";
+	sort    "$innxmit_accepted{$b} <=> $innxmit_accepted{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format        "%-27.27s";
+		value         "$key";
+		format_total  "TOTAL: %-20.20s";
+		total         "$num";
+        };
+        column {
+                name          "Offered";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innxmit_offered{$key}";
+		total         "total(%innxmit_offered)";
+        };
+        column {
+                name          "Taken";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innxmit_accepted{$key}";
+		total         "total(%innxmit_accepted)";
+        };
+        column {
+                name          "Refused";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innxmit_refused{$key}";
+		total         "total(%innxmit_refused)";
+        };
+        column {
+                name          "Reject";
+		format_name   "%7s";
+		format        "%7d";
+		value         "$innxmit_rejected{$key}";
+		total         "total(%innxmit_rejected)";
+        };
+	column {
+		name	      "Miss";
+		format_name   "%5s";
+		format	      "%5d";
+		value	      "$innxmit_missing{$key}";
+		total	      "total(%innxmit_rejected)";
+	};
+        column {
+                name          "%Acc";
+		format_name   "%4s";
+		format        "%3d%%";
+		value         "$innxmit_offered{$key} == 0 ? 0 :
+		       $innxmit_accepted{$key} / $innxmit_offered{$key} * 100";
+		total         "total(%innxmit_offered) == 0 ? 0 :
+		     total(%innxmit_accepted) / total(%innxmit_offered) * 100";
+        };
+        column {
+                name          "Elapsed";
+		format        "%8s";
+		value         "time($innxmit_times{$key})";
+		total         "time(total(%innxmit_times))";
+        };
+	graph {
+	        title         "Outgoing Feeds (innxmit)";
+		type          histo3d;
+		sort	      "%innxmit_accepted";
+		data {
+		          name    "Art. accepted";
+			  color   "#0000FF";
+			  value   "%innxmit_accepted";
+                };
+		data {
+		          name    "Art. refused";
+			  color   "#FFAF00";
+			  value   "%innxmit_refused";
+                };
+		data {
+		          name    "Art. rejected";
+			  color   "#FF0000";
+			  value   "%innxmit_rejected";
+                };
+		data {
+		          name    "Art. missing";
+			  color   "#00FF00";
+			  value   "%innxmit_missing";
+                };
+	};
+};
+
+section innxmit_volume {
+	title	"Outgoing Feeds (innxmit) by Volume:";
+	data	"%innxmit_accepted_size";
+	sort	"$innxmit_accepted_size{$b} <=> $innxmit_accepted_size{$a}";
+	numbering true;
+	column {
+		name		"Server";
+		format		"%-24.24s";
+		value		"$key";
+		format_total	"TOTAL: %-17.17s";
+		total		"$num";
+	};
+	column {
+		name		"AcceptVol";
+		format		"%9s";
+		value		"bytes($innxmit_accepted_size{$key})";
+		total		"bytes(total(%innxmit_accepted_size))";
+	};
+	column {
+		name		"RejectVol";
+		format		"%9s";
+		value		"bytes($innxmit_rejected_size{$key})";
+		total		"bytes(total(%innxmit_rejected_size))";
+	};
+	column {
+		name		"TotalVol";
+		format		"%9s";
+		value		"bytes($innxmit_accepted_size{$key} +
+	                               $innxmit_rejected_size{$key} +
+                                       $innxmit_bytes{$key})";
+		total		"bytes(total(%innxmit_accepted_size) +
+                                       total(%innxmit_rejected_size) +
+                                       total(%innxmit_bytes))";
+	};
+	column {
+		name		"KB/s";
+		format_name	"%5s";
+		format		"%5.1f";
+		value		"($innxmit_accepted_size{$key} +
+                                  $innxmit_rejected_size{$key} +
+                                  $innxmit_bytes{$key}) /
+                                    $innxmit_times{$key} / 1024";
+		total		"(total(%innxmit_accepted_size) +
+                                  total(%innxmit_rejected_size) +
+                                  total(%innxmit_bytes)) /
+                                    total(%innxmit_times) / 1024";
+	};
+	column {
+		name		"Vol/Art";
+		format		"%9s";
+		value		"bytes(($innxmit_accepted_size{$key} +
+                                        $innxmit_rejected_size{$key} +
+                                        $innxmit_bytes{$key}) /
+                                         ($innxmit_accepted{$key} +
+                                          $innxmit_rejected{$key}))";
+		total		"bytes((total(%innxmit_accepted_size) +
+                                        total(%innxmit_rejected_size) +
+        	                        total(%innxmit_bytes)) /
+                                         (total(%innxmit_accepted) +
+	                                  total(%innxmit_rejected)))";
+	};
+	column {
+		name		"Elapsed";
+		format		"%8s";
+		value		"time($innxmit_times{$key})";
+		total		"time(total(%innxmit_times))";
+	};
+	graph {
+		title		"Outgoing Feeds (innxmit)";
+		type		histo3d;
+		sort		"%innxmit_accepted";
+		data {
+			name	"Articles accepted";
+			color	"#0000FF";
+			value	"%innxmit_accepted_size";
+		};
+		data {
+			name	"Articles rejected";
+			color	"#FFAF00";
+			value	"%innxmit_rejected_size";
+		};
+		data {
+			name	"Total";
+			color	"#FF0000";
+			value	"%innxmit_missing";
+		};
+	};
+};
+
+
+section innxmit_connect2 {
+        title   "Outgoing Feeds (innxmit) - other information:";
+	data    "%innxmit_site";
+	sort    "$innxmit_accepted{$b} <=> $innxmit_accepted{$a}";
+	numbering true;
+        column {
+                name          "Server";
+		format        "%-25.25s";
+		value         "$key";
+		format_total  "TOTAL: %-18.18s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+		format_name   "%5s";
+		format        "%5d";
+		value         "$innxmit_site{$key}";
+		total         "total(%innxmit_site)";
+        };
+        column {
+                name          "Ok";
+		format_name   "%5s";
+		format        "%5d";
+		value         "$innxmit_site{$key} -
+		               ($innxmit_afail_host{$key} +
+			       $innxmit_hiload{$key} + $innxmit_nospace{$key} +
+			       $innxmit_cfail_host{$key} +
+			     $innxmit_expire{$key} + $innxmit_crefused{$key})";
+		total         "total(%innxmit_site) -
+		               (total(%innxmit_afail_host) +
+			       total(%innxmit_hiload) +
+			       total(%innxmit_nospace) +
+			       total(%innxmit_cfail_host) +
+			       total(%innxmit_expire) +
+			       total(%innxmit_crefused))";
+        };
+        column {
+                name          "Auth";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$innxmit_afail_host{$key}";
+		total         "total(%innxmit_afail_host)";
+        };
+        column {
+                name          "Load";
+		format_name   "%4s";
+		format        "%4d";
+		value         "$innxmit_hiload{$key}";
+		total         "total(%innxmit_hiload)";
+        };
+        column {
+                name          "Space";
+		format_name   "%5s";
+		format        "%5d";
+		value         "$innxmit_nospace{$key}";
+		total         "total(%innxmit_nospace)";
+        };
+        column {
+                name          "Expire";
+		format_name   "%6s";
+		format        "%6d";
+		value         "$innxmit_expire{$key}";
+		total         "total(%innxmit_expire)";
+        };
+        column {
+                name          "Connct";
+		format_name   "%6s";
+		format        "%6d";
+		value         "$innxmit_cfail_host{$key}";
+		total         "total(%innxmit_cfail_host)";
+        };
+        column {
+                name          "Other";
+		format_name   "%6s";
+		format        "%6d";
+		value         "$innxmit_crefused{$key}";
+		total         "total(%innxmit_crefused)";
+        };
+        column {
+                name          "Pct";
+		format_name   "%4s";
+		format        "%3d%%";
+		value         "$innxmit_site{$key} ? 100 *
+		               ($innxmit_site{$key} -
+			       ($innxmit_afail_host{$key} +
+			       $innxmit_hiload{$key} + $innxmit_nospace{$key} +
+			       $innxmit_cfail_host{$key} +
+			       $innxmit_expire{$key} +
+			  $innxmit_crefused{$key})) / $innxmit_site{$key} : 0";
+		total         "total(%innxmit_site) ?
+		               100 * (total(%innxmit_site) -
+			       (total(%innxmit_afail_host) +
+			       total(%innxmit_hiload) +
+			       total(%innxmit_nospace) +
+			       total(%innxmit_cfail_host) +
+			       total(%innxmit_expire) +
+			total(%innxmit_crefused))) / total(%innxmit_site) : 0";
+        };
+};
+
+section innxmit_unwanted {
+        title   "Sites fed by innxmit rejecting bad articles:";
+	data    "%innxmit_badart";
+	sort    "$innxmit_badart{$b} <=> $innxmit_badart{$a}";
+        column {
+	numbering true;
+                name          "Server";
+                format        "%-23.23s";
+		value         "$key";
+		format_total  "TOTAL: %-16.16s";
+		total         "$num";
+        };
+        column {
+                name          "Total";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$innxmit_badart{$key}";
+		total         "total(%innxmit_badart)";
+        };
+        column {
+                name          "Group";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$innxmit_uw_ng_s{$key}";
+		total         "total(%innxmit_uw_ng_s)";
+        };
+        column {
+                name          "Dist";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$innxmit_uw_dist_s{$key}";
+		total         "total(%innxmit_uw_dist_s)";
+        };
+        column {
+                name          "Duplic";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$innxmit_duplicate{$key}";
+		total         "total(%innxmit_duplicate)";
+        };
+        column {
+                name          "Unapp";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$innxmit_unapproved{$key}";
+		total         "total(%innxmit_unapproved)";
+        };
+        column {
+                name          "TooOld";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$innxmit_tooold{$key}";
+		total         "total(%innxmit_tooold)";
+        };
+        column {
+                name          "Site";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$innxmit_uw_site{$key}";
+		total         "total(%innxmit_uw_site)";
+        };
+        column {
+                name          "Line";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$innxmit_linecount{$key}";
+		total         "total(%innxmit_linecount)";
+        };
+        column {
+                name          "Other";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$innxmit_others{$key}";
+		total         "total(%innxmit_others)";
+        };
+};
+
+section crosspost {
+        title   "Crosspost stats:";
+	data    "%crosspost";
+        column {
+                name          "Events";
+                format        "%-63.63s";
+		value         "$key";
+		format_total  "TOTAL: %-56.56s";
+		total         "$num";
+        };
+        column {
+                name          "Number";
+		value         "$crosspost{$key}";
+		format        "%7s";
+		total         "total(%crosspost)";
+        };
+        column {
+                name          "Num/min";
+		value         "$crosspost_times{$key}";
+		format        "%7s";
+		total         "total(%crosspost_times)";
+        };
+};
+
+section batcher_elapsed {
+        title   "UUCP batches created:";
+	data    "%batcher_elapsed";
+        column {
+                name          "Server";
+                format        "%-41.41s";
+		value         "$key";
+		format_total  "TOTAL: %-34.34s";
+		total         "$num";
+        };
+        column {
+                name          "Offered";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$batcher_offered{$key}";
+		total         "total(%batcher_offered)";
+        };
+        column {
+                name          "Articles";
+                format_name   "%8s";
+                format        "%8d";
+		value         "$batcher_articles{$key}";
+		total         "total(%batcher_articles)";
+        };
+        column {
+                name          "Size";
+                format        "%10s";
+		value         "bytes($batcher_bytes{$key})";
+		total         "bytes(total(%batcher_bytes))";
+        };
+        column {
+                name          "Elapsed";
+                format        "%9s";
+		value         "time($batcher_elapsed{$key})";
+		total         "time(total(%batcher_elapsed))";
+        };
+};
+
+section rnews_host {
+        title   "Rnews articles offered from:";
+	data    "%rnews_host";
+	sort    "$rnews_host{$b} <=> $rnews_host{$a}";
+        column {
+                name          "System";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Offered";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$rnews_host{$key}";
+		total         "total(%rnews_host)";
+        };
+};
+
+section rnews_rejected {
+        title   "Rnews connections rejected:";
+	data    "%rnews_rejected";
+	sort    "$rnews_rejected{$b} <=> $rnews_rejected{$a}";
+        column {
+                name          "Reason";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$rnews_rejected{$key}";
+		total         "total(%rnews_rejected)";
+        };
+};
+
+section rnews_misc {
+        title   "Miscellaneous rnews statistics:";
+	data    "%rnews_misc";
+	sort    "$rnews_misc{$b} <=> $rnews_misc{$a}";
+	double  true;
+        column {
+	        primary       true;
+                name          "Event";
+		format        "%-69.69s";
+		value         "$key1";
+		format_total  "TOTAL: %-62.62s";
+		total         "";
+        };
+        column {
+                name          "Element";
+		format        "  %-67.67s";
+		value         "$key2";
+		total         "";
+        };
+        column {
+                name          "Number";
+		format_name   "%9s";
+		format        "%9d";
+		value         "$rnews_misc{$key1}{$key2}";
+		total         "total(%rnews_misc)";
+        };
+};
+
+section nnrpd_groups {
+        title   "NNRP readership statistics:";
+	data    "%nnrpd_connect";
+	sort    "$nnrpd_articles{$b} <=> $nnrpd_articles{$a}";
+	numbering true;
+        column {
+                name          "System";
+                format        "%-30.30s";
+		value         "$key";
+		format_total  "TOTAL: %-23.23s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$nnrpd_connect{$key}";
+		total         "total(%nnrpd_connect)";
+        };
+        column {
+                name          "Arts";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$nnrpd_articles{$key}";
+		total         "total(%nnrpd_articles)";
+        };
+	column {
+		name	      "Size";
+		format	      "%9s";
+		value	      "bytes($nnrpd_bytes{$key})";
+		total	      "bytes(total(%nnrpd_bytes))";
+        };
+        column {
+                name          "Groups";
+                format_name   "%6s";
+                format        "%6d";
+		value         "$nnrpd_groups{$key}";
+		total         "total(%nnrpd_groups)";
+        };
+        column {
+                name          "Post";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$nnrpd_post_ok{$key}";
+		total         "total(%nnrpd_post_ok)";
+        };
+        column {
+                name          "Rej";
+                format_name   "%4s";
+                format        "%4d";
+		value         "$nnrpd_post_rej{$key} +
+                               $nnrpd_post_error{$key}";
+		total         "total(%nnrpd_post_rej) +
+		               total(%nnrpd_post_error)";
+        };
+        column {
+                name          "Elapsed";
+                format        "%9s";
+		value         "time($nnrpd_times{$key})";
+		total         "time(total(%nnrpd_times))";
+        };
+};
+
+section nnrpd_dom_groups {
+        title   "NNRP readership statistics (by domain):";
+        data    "%nnrpd_dom_connect";
+        sort    "$nnrpd_dom_articles{$b} <=> $nnrpd_dom_articles{$a}";
+        numbering true;
+        column {
+                name          "System";
+                format        "%-30.30s";
+                value         "$key";
+                format_total  "TOTAL: %-23.23s";
+                total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%4s";
+                format        "%4d";
+                value         "$nnrpd_dom_connect{$key}";
+                total         "total(%nnrpd_dom_connect)";
+        };
+        column {
+                name          "Arts";
+                format_name   "%6s";
+                format        "%6d";
+                value         "$nnrpd_dom_articles{$key}";
+                total         "total(%nnrpd_dom_articles)";
+        };
+        column {
+                name          "Size";
+                format        "%9s";
+                value         "bytes($nnrpd_dom_bytes{$key})";
+                total         "bytes(total(%nnrpd_dom_bytes))";
+        };
+        column {
+                name          "Groups";
+                format_name   "%6s";
+                format        "%6d";
+                value         "$nnrpd_dom_groups{$key}";
+                total         "total(%nnrpd_dom_groups)";
+        };
+        column {
+                name          "Post";
+                format_name   "%4s";
+                format        "%4d";
+                value         "$nnrpd_dom_post_ok{$key}";
+                total         "total(%nnrpd_dom_post_ok)";
+        };
+        column {
+                name          "Rej";
+                format_name   "%4s";
+                format        "%4d";
+                value         "$nnrpd_dom_post_rej{$key} +
+	                       $nnrpd_dom_post_error{$key}";
+                total         "total(%nnrpd_dom_post_rej) +
+                               total(%nnrpd_dom_post_error)";
+        };
+        column {
+                name          "Elapsed";
+                format        "%9s";
+                value         "time($nnrpd_dom_times{$key})";
+                total         "time(total(%nnrpd_dom_times))";
+        };
+};
+
+section nnrpd_auth {
+        title   "NNRP auth users:";
+	data    "%nnrpd_auth";
+	top     20;
+	sort    "$nnrpd_auth{$b} <=> $nnrpd_auth{$a}";
+        column {
+                name          "User";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_auth{$key}";
+		total         "total(%nnrpd_auth)";
+        };
+};
+
+section nnrpd_resource {
+        title   "NNRP total resource statistics:";
+        data    "%nnrpd_resource_elapsed";
+	top     20;
+	sort    "$nnrpd_resource_elapsed{$b} <=> $nnrpd_resource_elapsed{$a}";
+        column {
+                name          "System";
+                format        "%-40.40s";
+		format_total  "TOTAL: %-33.33s";
+		value         "$key";
+                total         "$num";
+        };
+        column {
+                name          "User(ms)";
+                format_name   "%9s";
+                format        "%9.3f";
+		value         "$nnrpd_resource_user{$key}";
+                total         "total(%nnrpd_resource_user)";
+        };
+        column {
+                name          "System(ms)";
+                format_name   "%9s";
+                format        "%9.3f";
+		value         "$nnrpd_resource_system{$key}";
+                total         "total(%nnrpd_resource_system)";
+        };
+        column {
+                name          "Idle(ms)";
+                format_name   "%9s";
+                format        "%9.3f";
+		value         "$nnrpd_resource_idle{$key}";
+                total         "total(%nnrpd_resource_idle)";
+        };
+        column {
+                name          "Elapsed";
+		format_name   "%8s";
+		format        "%9s";
+		value         "time($nnrpd_resource_elapsed{$key})";
+                total         "time(total(%nnrpd_resource_elapsed))";
+        };
+};
+
+section nnrpd_curious {
+        title   "Curious NNRP server explorers:";
+	data    "%nnrpd_curious";
+	top     20;
+	sort    "$nnrpd_curious{$b} <=> $nnrpd_curious{$a}";
+        column {
+                name          "System";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_curious{$key}";
+		total         "total(%nnrpd_curious)";
+        };
+};
+
+section nnrpd_no_permission {
+        title   "NNRP no permission clients:";
+	data    "%nnrpd_no_permission";
+	sort    "$nnrpd_no_permission{$b} <=> $nnrpd_no_permission{$a}";
+        column {
+                name          "System";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_no_permission{$key}";
+		total         "total(%nnrpd_no_permission)";
+        };
+};
+
+section nnrpd_gethostbyaddr {
+        title   "NNRP gethostbyaddr failures:";
+	data    "%nnrpd_gethostbyaddr";
+	top     20;
+	sort    "$nnrpd_gethostbyaddr{$b} <=> $nnrpd_gethostbyaddr{$a}";
+        column {
+                name          "System";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_gethostbyaddr{$key}";
+		total         "total(%nnrpd_gethostbyaddr)";
+        };
+};
+
+section nnrpd_unrecognized {
+        title   "NNRP unrecognized commands (by host):";
+	data    "%nnrpd_unrecognized";
+	sort    "$nnrpd_unrecognized{$b} <=> $nnrpd_unrecognized{$a}";
+        column {
+                name          "System";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_unrecognized{$key}";
+		total         "total(%nnrpd_unrecognized)";
+        };
+};
+
+section nnrpd_unrecognized2 {
+        title   "NNRP unrecognized commands (by command):";
+	data    "%nnrpd_unrecogn_cmd";
+	sort    "$nnrpd_unrecogn_cmd{$b} <=> $nnrpd_unrecogn_cmd{$a}";
+        column {
+                name          "Command";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_unrecogn_cmd{$key}";
+		total         "total(%nnrpd_unrecogn_cmd)";
+        };
+};
+
+section nnrpd_timeout {
+        title   "NNRP client timeouts:";
+	data    "%nnrpd_timeout";
+	top     20;
+	sort    "$nnrpd_timeout{$b} <=> $nnrpd_timeout{$a}";
+        column {
+                name          "System";
+                format        "%-67.67s";
+		value         "$key";
+		format_total  "TOTAL: %-60.60s";
+		total         "$num";
+        };
+        column {
+                name          "Conn";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$nnrpd_timeout{$key}";
+		total         "total(%nnrpd_timeout)";
+        };
+        column {
+                name          "Peer";
+                format_name   "%5s";
+                format        "%5d";
+		value         "$nnrpd_reset_peer{$key}";
+		total         "total(%nnrpd_reset_peer)";
+        };
+};
+
+section nnrpd_hierarchy {
+        title   "Newsgroup request counts (by category):";
+	data    "%nnrpd_hierarchy";
+	sort    "$nnrpd_hierarchy{$b} <=> $nnrpd_hierarchy{$a}";
+	numbering true;
+        column {
+                name          "Category";
+                format        "%-64.64s";
+		value         "$key";
+		format_total  "TOTAL: %-57.57s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_hierarchy{$key}";
+		total         "total(%nnrpd_hierarchy)";
+        };
+        column {
+                name          "Pct";
+                format_name   "%6s";
+                format        "%5.1f%%";
+		value         "$nnrpd_hierarchy{$key} /
+		               total(%nnrpd_hierarchy) * 100";
+		total         "100";
+        };
+	# graph : type piechart
+};
+
+section nnrpd_group {
+        title   "Newsgroup request counts (by newsgroup):";
+	data    "%nnrpd_group";
+	sort    "$nnrpd_group{$b} <=> $nnrpd_group{$a}";
+	top     100;
+	numbering true;
+        column {
+                name          "Newsgroup";
+                format        "%-71.71s";
+		value         "$key";
+		format_total  "TOTAL: %-64.64s";
+		total         "$num";
+        };
+        column {
+                name          "Count";
+                format_name   "%7s";
+                format        "%7d";
+		value         "$nnrpd_group{$key}";
+		total         "total(%nnrpd_group)";
+        };
+};
+
+section ihave_site {
+	title	"IHAVE messages offered from:";
+	data	"%controlchan_ihave_site";
+	sort	"$controlchan_ihave_site{$b} <=> $controlchan_ihave_site{$a}";
+	column {
+		name		"System";
+		format		"%-71.71s";
+		value		"$key";
+		format_total	"TOTAL: %-64.64s";
+		total		"$num";
+	};
+	column {
+		name		"Offered";
+		format_name	"%7s";
+		format		"%7d";
+		value		"$controlchan_ihave_site{$key}";
+		total		"total(%controlchan_ihave_site)";
+	};
+};
+
+section sendme_site {
+	title	"SENDME messages offered from:";
+	data	"%controlchan_sendme_site";
+	sort	"$controlchan_sendme_site{$b} <=>
+	         $controlchan_sendme_site{$a}";
+	column {
+		name		"System";
+		format		"%-71.71s";
+		value		"$key";
+		format_total	"TOTAL: %-64.64s";
+		total		"$num";
+	};
+	column {
+		name		"Offered";
+		format_name	"%7s";
+		format		"%7d";
+		value		"$controlchan_sendme_site{$key}";
+		total		"total(%controlchan_sendme_site)";
+	};
+};
--- /dev/null
+++ b/extra/innreport
@@ -0,0 +1,2594 @@
+#! /usr/bin/perl
+require '/usr/lib/news/innshellvars.pl';
+
+##########################################################################
+#
+#   innreport: Perl script to summarize news log files
+#              (with optional HTML output and graphs).
+#
+# version: 3.0.2
+#
+# Copyright (c) 1996-1999, Fabien Tassin (fta@sofaraway.org).
+#
+##########################################################################
+#
+# Usage: innreport -f config_file [-[no]options] logfile [logfile2 [...]]
+#   where options are:
+#     -h (or -help)      : this help page
+#     -html              : HTML output
+#     -v                 : display the version number of INNreport
+#     -f config_file     : name of the configuration file
+#     -config            : print INNreport configuration information
+#     -g                 : want graphs [default]
+#     -graph             : an alias for option -g
+#     -d directory       : directory for Web pages
+#     -dir directory     : an alias for option -d
+#     -p directory       : pictures path (file space)
+#     -path directory    : an alias for option -p
+#     -w directory       : pictures path (web space)
+#     -webpath directory : an alias for option -w
+#     -i                 : name of index page
+#     -index             : an alias for option -i
+#     -a                 : want to archive HTML results
+#     -archive           : an alias for option -a
+#     -c number          : how many report files to keep (0 = all)
+#     -cycle number      : an alias for option -c
+#     -s char            : separator for filename
+#     -separator char    : an alias for option -s
+#     -unknown           : Unknown entries from news log file
+#     -maxunrec          : Max number of unrecognized line to display
+#     -casesensitive     : Case sensitive
+#     -notdaily          : Never perform daily actions
+#
+# Use no in front of boolean options to unset them.
+# For example, "-html" is set by default. Use "-nohtml" to remove this
+# feature.
+#
+##########################################################################
+#
+# ABSOLUTELY NO WARRANTY WITH THIS PACKAGE. USE IT AT YOUR OWN RISKS.
+#
+# Note: You need the Perl graphic library GD.pm if you want the graphs.
+#       GD is available on all good CPAN ftp sites:
+#           ex: [CPAN_DIR]/authors/id/LDS/GD-1.1_.tar.gz (or greater)
+#         or directly to:
+#           <URL:http://www-genome.wi.mit.edu/pub/software/WWW/GD.html>
+#       Note : innreport will create PNG or GIF files depending upon
+#              the GD version.
+#
+# Documentation: for a short explaination of the different options, you
+#        can read the usage (obtained with the -h or -help switch).
+#
+# Install: - check the Perl location (first line). Require Perl 5.002
+#            or greater.
+#          - look at the parameters in the configuration file (section
+#            'default')
+#          - copy the configuration file into ${PATHETC}/innreport.conf
+#          - copy the INN module into ${PATHETC}/innreport_inn.pm
+#          - copy this script into ${PATHETC}/innreport
+#          - be sure that the news user can run it (chmod 755 or 750)
+#          - in "scanlog", comment the line containing innlog and add:
+#            ${PATHETC}/innreport -f ${PATHETC}/innreport.conf ${OLD_SYSLOG}
+#            or, if you want to change some options:
+#    ${PATHETC}/innreport -f ${PATHETC}/innreport.conf options ${OLD_SYSLOG}
+#
+# Report: please report bugs (preferably) to the innreport mailing list
+#         (see below) or directly to the author (do not forget to
+#         include the result of the "-config" switch, the parameters
+#         passed on the command line and the INN version).
+#         Please also report unknown entries.
+#         Be sure your are using the latest version of this script before
+#         any report.
+#         (check <URL:ftp://ftp.sofaraway.org/pub/innreport/>)
+#
+##########################################################################
+
+# remember to add '-w' on the first line and to uncomment the 'use strict'
+# below before doing any changes to this file.
+
+use strict;
+
+## Do you want to create a Web page. Pick DO or DONT.
+my $HTML = "DONT";
+
+## Do you want the graphs (need $HTML too). Pick DO or DONT.
+my $GRAPH = "DO";
+
+## Directory for the Web pages (used only if the previous line is active)
+my $HTML_dir = "/var/www/News/stats";
+
+## Directory for the pictures (need HTML support) in the file space
+my $IMG_dir = "$HTML_dir/pics";
+
+## Directory for the pictures (need HTML support) in the Web space
+## (can be relative or global)
+my $IMG_pth = "pics";
+
+## Do you want to archive HTML results (& pics) [ will add a date in each
+## name ]. Pick DO or DONT.
+my $ARCHIVE = "DO";
+
+## index page will be called:
+my $index = "index.html";
+
+## How many report files to keep (0 = all) (need $ARCHIVE).
+my $CYCLE = 30;
+
+## separator between hours-minutes-seconds in filenames
+## (normaly a ":" but some web-browsers (Lynx, MS-IE, Mosaic) can't read it)
+## Warning: never use "/". Use only a _valid_ filename char.
+my $SEPARATOR = ".";
+
+## Do you want the "Unknown entries from news log file" report. Pick DO or
+## DONT.
+my $WANT_UNKNOWN = "DO";
+
+## Max number of unrecognized lines to display (if $WANT_UNKNOWN)
+## (-1 = no limit)
+my $MAX_UNRECOGNIZED = 50;
+
+## Do you want to be case sensitive. Pick DO or DONT.
+my $CASE_SENSITIVE = "DO";
+
+## Some actions must only be performed daily (once for a log file).
+## (ex: unwanted.log with INN). Default value (DONT) means to perform
+## these actions each . Pick DO or DONT.
+my $NOT_DAILY = "DONT";
+
+###############################################
+## THERE'S NOTHING TO CHANGE AFTER THIS LINE ##
+###############################################
+
+my $version = "3.0.2";
+my %output; # content of the configuration file.
+my $DEBUG = 0; # set to 1 to verify the structure/content of the conf file.
+my $start_time = time;
+
+# Require Perl 5.002 or greater.
+require 5.002;
+use Getopt::Long;
+use vars qw/$HAVE_GD $GD_FORMAT/;
+
+my @old_argv = @ARGV;
+
+# Convert DO/DONT into boolean values.
+{
+  my $i;
+  foreach $i (\$HTML, \$GRAPH, \$ARCHIVE, \$WANT_UNKNOWN,
+              \$CASE_SENSITIVE, \$NOT_DAILY) {
+    $$i = $$i eq 'DO' ? 1 : 0 ;
+  }
+}
+
+my %ref;
+GetOptions (\%ref,
+	   qw(-h -help
+	      -html!
+	      -config
+	      -f=s
+	      -g! -graph!
+	      -d=s -dir=s
+	      -p=s -path=s
+	      -w=s -webpath=s
+              -i=s -index=s
+	      -a! -archive!
+	      -c=i -cycle=i
+	      -s=s -separator=s
+	      -unknown!
+	      -html-unknown!
+	      -maxunrec=i
+	      -casesensitive!
+              -notdaily!
+	      -v
+	      ));
+
+&Version if $ref{'v'};
+
+&Decode_Config_File($ref{'f'}) if defined $ref{'f'};
+&Usage if $ref{'h'} || $ref{'help'} || !defined $ref{'f'};
+
+$HTML = 0 if defined $output{'default'}{'html'};
+$HTML = 1 if $output{'default'}{'html'} eq 'true';
+$HTML = 0 if defined $ref{'html'};
+$HTML = 1 if $ref{'html'};
+
+$GRAPH = 0 if defined $output{'default'}{'graph'};
+$GRAPH = 1 if $HTML && ($output{'default'}{'graph'} eq 'true');
+$GRAPH = 0 if defined $ref{'g'} || defined $ref{'graph'};
+$GRAPH = 1 if $HTML && ($ref{'g'} || $ref{'graph'});
+
+$HTML_dir = &GetValue ($output{'default'}{'html_dir'})
+  if defined $output{'default'}{'html_dir'};
+$HTML_dir = $ref{'d'} if defined $ref{'d'};
+$HTML_dir = $ref{'dir'} if defined $ref{'dir'};
+
+$IMG_pth = &GetValue ($output{'default'}{'img_dir'})
+  if defined $output{'default'}{'img_dir'};
+$IMG_pth = $ref{'w'} if defined $ref{'w'};
+$IMG_pth = $ref{'webpath'} if defined $ref{'webpath'};
+
+$IMG_dir = $HTML_dir . "/" . $IMG_pth
+  if (defined $output{'default'}{'html_dir'} ||
+       defined $ref{'w'} || defined $ref{'webpath'})
+      &&
+      (defined $output{'default'}{'html_dir'} ||
+       defined $ref{'d'} || defined $ref{'dir'});
+
+$IMG_dir = $ref{'p'} if defined $ref{'p'};
+$IMG_dir = $ref{'path'} if defined $ref{'path'};
+
+$index = &GetValue ($output{'default'}{'index'})
+  if defined $output{'default'}{'index'};
+$index = $ref{'i'} if defined $ref{'i'};
+$index = $ref{'index'} if defined $ref{'index'};
+
+$ARCHIVE = &GetValue ($output{'default'}{'archive'})
+  if defined $output{'default'}{'archive'};
+$ARCHIVE = $ARCHIVE eq 'true';
+$ARCHIVE = 0 if defined $ref{'a'} || defined $ref{'archive'};
+$ARCHIVE = 1 if ($ref{'a'} || $ref{'archive'}) && $HTML;
+$ARCHIVE = 0 unless $HTML;
+
+$CYCLE = &GetValue ($output{'default'}{'cycle'})
+  if defined $output{'default'}{'cycle'};
+$CYCLE = 0 if $CYCLE eq 'none';
+$CYCLE = $ref{'c'} if defined $ref{'c'};
+$CYCLE = $ref{'cycle'} if defined $ref{'cycle'};
+
+$SEPARATOR = &GetValue ($output{'default'}{'separator'})
+  if defined $output{'default'}{'separator'};
+$SEPARATOR = $ref{'s'} if defined $ref{'s'};
+$SEPARATOR = $ref{'separator'} if defined $ref{'separator'};
+
+if (defined $output{'default'}{'unknown'}) {
+  $WANT_UNKNOWN = &GetValue ($output{'default'}{'unknown'});
+  $WANT_UNKNOWN = $WANT_UNKNOWN eq 'true' ? 1 : 0;
+}
+$WANT_UNKNOWN = 0 if defined $ref{'unknown'};
+$WANT_UNKNOWN = 1 if $ref{'unknown'};
+
+my $WANT_HTML_UNKNOWN = $WANT_UNKNOWN;
+if (defined $output{'default'}{'html-unknown'}) {
+  $WANT_HTML_UNKNOWN = &GetValue ($output{'default'}{'html-unknown'});
+  $WANT_HTML_UNKNOWN = $WANT_HTML_UNKNOWN eq 'true' ? 1 : 0;
+}
+$WANT_HTML_UNKNOWN = 0 if defined $ref{'html-unknown'};
+$WANT_HTML_UNKNOWN = 1 if $ref{'html-unknown'};
+
+$NOT_DAILY = 0 if defined $ref{'notdaily'};
+$NOT_DAILY = 1 if $ref{'notdaily'};
+
+$MAX_UNRECOGNIZED = &GetValue ($output{'default'}{'max_unknown'})
+  if defined $output{'default'}{'max_unknown'};
+$MAX_UNRECOGNIZED = $ref{'maxunrec'} if defined ($ref{'maxunrec'});
+
+$CASE_SENSITIVE = &GetValue ($output{'default'}{'casesensitive'})
+  if defined $output{'default'}{'casesensitive'};
+$CASE_SENSITIVE = 1 if $CASE_SENSITIVE eq 'true';
+$CASE_SENSITIVE = 0 if defined $ref{'casesensitive'};
+$CASE_SENSITIVE = 1 if $ref{'casesensitive'};
+
+my $CLASS   = &GetValue ($output{'default'}{'module'});
+my $LIBPATH = &GetValue ($output{'default'}{'libpath'});
+
+umask 022;
+
+BEGIN {
+  eval "use GD;";
+  $HAVE_GD = $@ eq '';
+  if ($HAVE_GD) {
+    my $gd = new GD::Image(1,1);
+    $GD_FORMAT = "gif" if $gd->can('gif');
+    $GD_FORMAT = "png" if $gd->can('png');
+  }
+  $HAVE_GD;
+};
+undef $GRAPH unless $HTML;
+if ($GRAPH && !$::HAVE_GD) {
+  print "WARNING: can't make graphs as required.\n" .
+        "         Install GD.pm or disable this option.\n\n";
+  undef $GRAPH;
+}
+
+if ($HTML) {
+  if ($GRAPH) {
+    $IMG_dir = "." if defined $IMG_dir && $IMG_dir eq '';
+    $IMG_pth .= "/" if $IMG_pth;
+    $IMG_pth =~ s|/+|/|g;
+    $IMG_dir =~ s|/+|/|g;
+    unless (-w $IMG_dir) {
+      print "WARNING: can't write in \"$IMG_dir\" as required by -g " .
+	"switch.\n         Option -g removed. Please see the -p switch.\n\n";
+      undef $GRAPH;
+    }
+  }
+  $HTML_dir = "." if defined $HTML_dir && $HTML_dir eq '';
+  unless (-w $HTML_dir) {
+    print "WARNING: can't write in \"$HTML_dir\" as required by -html " .
+      "switch.\n         Option -html and -a removed. Please see the " .
+      "-d switch.\n\n";
+    undef $HTML;
+    $ARCHIVE = 0;
+  }
+}
+
+# Now, we are sure that HTML and graphs can be made if options are active.
+&Summary if defined $ref{'config'};
+
+my $unrecognize_max = 0;
+my @unrecognize;
+my ($total_line, $total_size) = (0, 0);
+my ($suffix, $HTML_output, %config, $first_date, $last_date,
+    %prog_type, %prog_size);
+
+my $HTML_header = '';
+my $HTML_footer = '';
+
+my $MIN = 1E10;
+my $MAX = -1;
+
+my $xmax = &GetValue ($output{'default'}{'graph_width'})   # Graph size..
+  if defined $output{'default'}{'graph_width'};
+$xmax = 550 unless $xmax;
+
+my $transparent = &GetValue ($output{'default'}{'transparent'})
+  if defined $output{'default'}{'transparent'};
+$transparent = (defined $transparent && $transparent eq 'true') ? 1 : 0;
+
+my $repeated = 1;
+
+my $first_date_cvt = $MIN;
+my $last_date_cvt = $MAX;
+
+
+#########################################################################
+my $s = sprintf "use lib qw($LIBPATH); use $CLASS;";
+eval $s;  # initialization
+die "Can't find/load $CLASS.pm : $@\n" if $@;
+
+my $save_line = <>;
+$_ = $save_line;
+local $^W = 0 if $] < 5.004; # to avoid a warning for each '+=' first use.
+LINE: while (!eof ()) {
+  $total_line++;
+  my $size = length;
+  $total_size += $size;
+
+  # Syslog optimization
+  if ($repeated) {
+    $repeated--;
+    $_ = $save_line;
+  }
+  else {
+    $_ = <>;
+    if ($_ =~ /last message repeated (\d+) times?$/o) {
+       $repeated = $1;
+       $_ = $save_line;
+    }
+    else {
+       $save_line = $_;
+    }
+  }
+
+  # skip empty lines
+  next LINE if $_ eq '';
+
+  my $res;
+  my ($day, $hour, $prog, $left) =
+    $_ =~ m/^(\S+\s+\S+) (\S+) \S+ (\S+): \[ID \d+ \S+\] (.*)$/o;
+  ($day, $hour, $prog, $left) =
+    $_ =~ m/^(\S+\s+\S+) (\S+) \S+ (\S+): (.*)$/o unless $day;
+  ($day, $hour, $prog, $left) =
+    $_ =~ m/^(\S+\s+\S+) (\S+) \d+ \S+ (\S+): (.*)$/o unless $day;
+
+  unless ($day) {
+    ($day, $hour, $res, $left) = $_ =~ m/^(\S+\s+\S+) (\S+)\.\d+ (\S+) (.*)$/o;
+    if ($day) {
+      my $cvtdate = &ConvDate ("$day $hour");
+      if ($cvtdate < $first_date_cvt) {
+	$first_date_cvt = $cvtdate;
+	$first_date = "$day $hour";
+      }
+      elsif ($cvtdate > $last_date_cvt) {
+	$last_date_cvt = $cvtdate;
+	$last_date = "$day $hour";
+      }
+      $prog = "inn";
+    }
+    else {
+      next if $_ =~ /^$/;
+      # Unrecognize line... skip
+      $unrecognize[$unrecognize_max] = $_
+	unless $unrecognize_max > $MAX_UNRECOGNIZED
+		&& $MAX_UNRECOGNIZED > 0;
+      $unrecognize_max++;
+      next LINE;
+    }
+  }
+  else {
+    my $cvtdate = &ConvDate ("$day $hour");
+    if ($cvtdate < $first_date_cvt) {
+      $first_date_cvt = $cvtdate;
+      $first_date = "$day $hour";
+    }
+    elsif ($cvtdate > $last_date_cvt) {
+      $last_date_cvt = $cvtdate;
+      $last_date = "$day $hour";
+    }
+  }
+
+  ########
+  ## Program name
+  # word[7164] -> word
+  my ($pid) = $prog =~ s/\[(\d+)\]$//o;
+  # word: -> word
+  $prog =~ s/:$//o;
+  # wordX -> word   (where X is a digit)
+  $prog =~ s/\d+$//o;
+
+  $prog_type{$prog}++;
+  $prog_size{$prog} = 0 unless defined $prog_size{$prog}; # stupid warning :(
+  $prog_size{$prog} += $size;
+
+  # The "heart" of the tool.
+  {
+    no strict;
+    next LINE if
+      &{$CLASS."::collect"} ($day, $hour, $prog, $res, $left, $CASE_SENSITIVE);
+  }
+
+  $unrecognize[$unrecognize_max] = $_
+    unless $unrecognize_max > $MAX_UNRECOGNIZED
+	    && $MAX_UNRECOGNIZED > 0;
+  $unrecognize_max++;
+}
+
+{
+  no strict;
+  &{$CLASS . "::adjust"} ($first_date, $last_date);
+}
+
+$| = 1;
+
+die "no data. Abort.\n" unless $total_line;
+
+my $sec_glob = &ConvDate ("$last_date") - &ConvDate ("$first_date");
+unless ($sec_glob) {
+  print "WARNING: bad date (\"$last_date\" or \"$first_date\")\n" .
+        "         Please, contact the author of innreport.\n";
+  $sec_glob = 24 * 60 * 60; # one day
+}
+
+$HTML_output = '';
+
+if ($HTML) {
+  # Create a new filename (unique and _sortable_)
+  if ($ARCHIVE) {
+    # The filename will contain the first date of the log or the current time.
+    my ($ts, $tm, $th, $dd, $dm, $dy) = localtime;
+    my ($m, $d, $h, $mn, $s) =
+      $first_date =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)$/;
+    if ($m) {
+      my $ddm = (index "JanFebMarAprMayJunJulAugSepOctNovDec", $m) / 3;
+      # Adjust the year because syslog doesn't record it. We assume that
+      # it's the current year unless the last date is in the future.
+      my $ld = &ConvDate($last_date);
+      $dy-- if $ld > $ts + 60 * ($tm + 60 * ($th + 24 * ($dd - 1 +
+        substr("000031059090120151181212243273304334", $dm * 3, 3)))) ||
+        $ld < &ConvDate($first_date);
+      ($dm, $dd, $th, $tm, $ts) = ($ddm, $d, $h, $mn, $s);
+    }
+    $dm++; # because January = 0 and we prefer 1
+    $dy += 100 if $dy < 90; # Try to pacify the year 2000 !
+    $dy += 1900;
+    $suffix = sprintf ".%02d.%02d.%02d-%02d$SEPARATOR%02d$SEPARATOR%02d",
+		       $dy, $dm, $dd, $th, $tm, $ts;
+  }
+  else {
+    $suffix = '';
+  }
+  $HTML_output = "$HTML_dir" . "/news-notice" . "$suffix" . ".html";
+  $HTML_output =~ s|/+|/|g;
+  if (defined $output{'default'}{'html_header_file'}) {
+    my $file = &GetValue ($output{'default'}{'html_header_file'});
+    $file = $HTML_dir . "/" . $file;
+    open (F, $file) && do {
+      local $/ = undef;
+      $HTML_header = <F>;
+      close F;
+    };
+  }
+  if (defined $output{'default'}{'html_footer_file'}) {
+    my $file = &GetValue ($output{'default'}{'html_footer_file'});
+    $file = $HTML_dir . "/" . $file;
+    open (F, $file) && do {
+      local $/ = undef;
+      $HTML_footer = <F>;
+      close F;
+    };
+  }
+}
+
+&Write_all_results ($HTML_output, \%output);
+
+&Make_Index ($HTML_dir, $index, "news-notice$suffix.html", \%output)
+  if $HTML && $index;
+
+#====================================================================
+
+if ($ARCHIVE) {
+  # rotate html files
+  &Rotate ($CYCLE, $HTML_dir, "news-notice", ".html");
+
+  # rotate pictures
+  my $report;
+  foreach $report (@{$output{'_order_'}}) {
+    next if $report =~ m/^(default|index)$/;
+    next unless defined $output{$report}{'graph'};
+
+    my $i = 0;
+    while ($GRAPH && defined ${${$output{$report}{'graph'}}[$i]}{'type'}) {
+      my $name = $report . ($i ? $i : '');
+      &Rotate ($CYCLE, $IMG_dir, $name, '.' . $GD_FORMAT);
+      $i++;
+    }
+  }
+}
+
+# Code needed by INN only. It must be in innreport_inn.pm to keep things clean.
+if (!$NOT_DAILY && defined $output{'default'}{'unwanted_log'}) {
+  my $logfile = &GetValue ($output{'default'}{'unwanted_log'});
+  my $logpath = &GetValue ($output{'default'}{'logpath'});
+  {
+    no strict;
+    &{$CLASS . "::report_unwanted_ng"} ("$logpath/$logfile");
+  }
+}
+
+################
+# End of report.
+###################################################################
+
+######
+# Misc...
+
+# Compare 2 dates (+hour)
+sub DateCompare {
+  # ex: "May 12 06"   for May 12, 6:00am
+  # The 2 dates are near. The range is less than a few days that's why we
+  # can cheat to determine the order. It is only important if one date
+  # is in January and the other in December.
+
+  my $date1 = substr ($a, 4, 2) * 24;
+  my $date2 = substr ($b, 4, 2) * 24;
+  $date1 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($a,0,3)) * 288;
+  $date2 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($b,0,3)) * 288;
+  if ($date1 - $date2 > 300 * 24) {
+    $date2 += 288 * 3 * 12;
+  }
+  elsif ($date2 - $date1 > 300 * 24) {
+    $date1 += 288 * 3 * 12;
+  }
+  $date1 += substr($a, 7, 2);
+  $date2 += substr($b, 7, 2);
+  $date1 - $date2;
+}
+
+
+# Convert: seconds to hh:mm:ss
+sub second2time {
+  my $temp;
+  my $t = shift;
+  # Hours
+  $temp = sprintf "%02d", $t / 3600;
+  my $chaine = "$temp:";
+  $t %= 3600;
+  # Min
+  $temp = sprintf "%02d", $t / 60;
+  $chaine .= "$temp:";
+  $t %= 60;
+  # Sec
+  $chaine .= sprintf "%02d", $t;
+  return $chaine;
+}
+
+# Convert: milliseconds to hh:mm:ss:mm
+sub ms2time {
+  my $temp;
+  my $t = shift;
+  # Hours
+  $temp = sprintf "%02d", $t / 3600000;
+  my $chaine = "$temp:";
+  $t %= 3600000;
+  # Min
+  $temp = sprintf "%02d", $t / 60000;
+  $chaine .= "$temp:";
+  $t %= 60000;
+  # Sec
+  $temp = sprintf "%02d", $t / 1000;
+  $chaine .= "$temp.";
+  $t %= 1000;
+  # Millisec
+  $chaine .= sprintf "%03d", $t;
+  return $chaine;
+}
+
+# Rotate the archive files..
+sub Rotate {
+  # Usage: &Rotate ($max_files, "$directory", "prefix", "suffix");
+  my ($max, $rep, $prefix, $suffix) = @_;
+  my ($file, $num, %files);
+  local ($a, $b);
+
+  return 1 unless $max;
+  opendir (DIR, "$rep") || die "Error: Cant open directory \"$rep\"\n";
+
+  FILE : while (defined ($file = readdir (DIR))) {
+    next FILE
+      unless $file =~ /^           # e.g. news-notice.1997.05.14-01:34:29.html
+                        $prefix          # Prefix : news-notice
+                        \.               # dot    : .
+	                (\d\d)?\d\d      # Year   : 1997 (or 97)
+	                \.               # dot    : .
+	                \d\d             # Month  : 05
+	                \.               # dot    : .
+                        \d\d             # Day    : 14
+                        -                # Separator : -
+                        \d\d             # Hour   : 01
+                        $SEPARATOR       # Separator : ":"
+                        \d\d             # Minute : 34
+                        $SEPARATOR       # Separator : ":"
+                        \d\d             # Second : 29
+                        $suffix          # Suffix : ".html"
+                        $/x;
+    $files{$file}++;
+  }
+  closedir DIR;
+  $num = 0;
+  foreach $file (sort {$b cmp $a} (keys (%files))) {
+    unlink "$rep/$file" if $num++ >= $max && -f "$rep/$file";
+  }
+  return 1;
+}
+
+# convert a date to a number of seconds
+sub ConvDate {
+  # usage: $num = &ConvDate ($date);
+  # date format is Aug 22 01:49:40
+  my $T = shift;
+  my ($m, $d, $h, $mn, $s) = $T =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)$/;
+  my $out = $s + 60 * $mn + 3600 * $h + 86400 * ($d - 1);
+
+  $m = substr("000031059090120151181212243273304334",
+	      index ("JanFebMarAprMayJunJulAugSepOctNovDec", $m), 3);
+  $out += $m * 86400;
+  return $out;
+}
+
+# Compare 2 filenames
+sub filenamecmp {
+  my ($la, $lb) = ($a, $b);
+  my ($ya) = $la =~ m/news-notice\.(\d+)\./o;
+  $ya += 100  if $ya < 90; # Try to pacify the year 2000 !
+  $ya += 1900 if $ya < 1900; # xx -> xxxx
+  my ($yb) = $lb =~ m/news-notice\.(\d+)\./o;
+  $yb += 100  if $yb < 90; # Try to pacify the year 2000 !
+  $yb += 1900 if $yb < 1900; # xx -> xxxx
+
+  $la =~ s/news-notice\.(\d+)\./$ya\./;
+  $lb =~ s/news-notice\.(\d+)\./$yb\./;
+  $la =~ s/[\.\-\:html]//g;
+  $lb =~ s/[\.\-\:html]//g;
+
+  $lb <=> $la;
+}
+
+sub ComputeTotal {
+  my $h = shift;
+  my $total = 0;
+  my $key;
+  foreach $key (keys (%$h)) {
+    $total += $$h{$key};
+  }
+  $total;
+}
+
+sub ComputeTotalDouble {
+  my $h = shift;
+  my $total = 0;
+  my ($key1, $key2);
+  foreach $key1 (keys (%$h)) {
+    foreach $key2 (keys (%{$$h{$key1}})) {
+      $total += ${$$h{$key1}}{$key2};
+    }
+  }
+  $total;
+}
+
+# make an index for archive pages
+sub Make_Index {
+  my ($rep, $index, $filename, $data) = @_;
+  my %output = %$data;
+
+  $index =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+
+  # add requested data at the end of the database.
+  open (DATA, ">> $rep/innreport.db") || die "can't open $rep/innreport.db\n";
+  my $i = 0;
+  my $res = "$filename";
+  while (defined ${${$output{'index'}{'column'}}[$i]}{'value'}) {
+    my $data = &GetValue (${${$output{'index'}{'column'}}[$i]}{'value'});
+    $data =~ s/\n//sog;
+    my @list = split /\|/, $data;
+    my $val;
+    foreach $val (@list) {
+      $res .= ($val eq 'date' ? "|$first_date -- $last_date"
+	                      : "|" . &EvalExpr($val));
+    }
+    $i++;
+  }
+  print DATA "$res\n";
+  close DATA;
+
+  # sort the database (reverse order), remove duplicates.
+  open (DATA, "$rep/innreport.db") || die "can't open $rep/innreport.db\n";
+  my %data;
+  while (<DATA>) {
+    m/^([^\|]+)\|(.*)$/o;
+    $data{$1} = $2;
+  }
+  close DATA;
+  open (DATA, "> $rep/innreport.db") || die "can't open $rep/innreport.db\n";
+  $i = 0;
+  foreach (sort {$b cmp $a} (keys %data)) {
+    print DATA "$_|$data{$_}\n" if $CYCLE == 0 || $i < $CYCLE;
+    $i++;
+  }
+  close DATA;
+
+  my $title = "Daily Usenet report";
+  $title = &GetValue ($output{'default'}{'title'})
+    if defined $output{'default'}{'title'};
+  $title =~ s/\\\"/\"/g;
+  my $Title = $title;
+  $Title =~ s/<.*?>//g;
+  my $body = '';
+  $body = &GetValue ($output{'default'}{'html_body'})
+    if defined $output{'default'}{'html_body'};
+  $body =~ s/\\\"/\"/go;
+  my $result = sprintf <<EOF;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML><HEAD>
+<TITLE>$Title: index</TITLE>
+</HEAD><BODY $body>
+$HTML_header
+<HR ALIGN=CENTER SIZE=\"4\" WIDTH=\"100%%\">
+<BR><CENTER><FONT SIZE=\"+2\">
+<B>$title - archives</B>
+</FONT></CENTER>
+<BR CLEAR=ALL>
+<HR ALIGN=CENTER SIZE=4 WIDTH=\"100%%\"><P>
+<CENTER>
+EOF
+
+  if ($GRAPH) {
+    my $i = 0;
+    while (defined ${${$output{'index'}{'graph'}}[$i]}{'title'}) {
+      my $title =  &GetValue (${${$output{'index'}{'graph'}}[$i]}{'title'});
+      my $filename = "index$i.$GD_FORMAT";
+      my $color_bg = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'color'});
+      my $unit     = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'unit'});
+      my $date_idx = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'value'});
+      $date_idx =~ s/^val(\d+)$/$1/o;
+      my @c = @{${${$output{'index'}{'graph'}}[$i]}{'data'}};
+      my $label_in  = &GetValue (${$c[0]}{'name'});
+      my $color_in  = &GetValue (${$c[0]}{'color'});
+      my $value_in  = &GetValue (${$c[0]}{'value'});
+      my $type_in   = 0;
+      $type_in = $value_in =~ s/^byte\((.*?)\)$/$1/o;
+      $value_in =~ s/^val(\d+)$/$1/o;
+      my $label_out = &GetValue (${$c[1]}{'name'});
+      my $color_out = &GetValue (${$c[1]}{'color'});
+      my $value_out = &GetValue (${$c[1]}{'value'});
+      my $type_out   = 0;
+      $type_out = $value_out =~ s/^byte\((.*?)\)$/$1/o;
+      $value_out =~ s/^val(\d+)$/$1/o;
+      my (%in, %out, %dates, $k);
+      foreach $k (keys (%data)) {
+	my @res = split /\|/, $data{$k};
+	my ($year) = $k =~ m/^news-notice\.(\d+)\.\d+\.\d+-\d+.\d+.\d+\.html/;
+	next unless $year; # bad filename.. strange.
+	my ($start, $end) =
+	  $res[$date_idx - 1] =~ m/^(\w+\s+\d+ \S+) -- (\w+\s+\d+ \S+)$/o;
+	next unless $start; # bad date
+	$start = &ConvDate ($start);
+	$end = &ConvDate ($end);
+	# 31/12 - 1/1 ?
+	my $inc = $end < $start ? 1 : 0;
+	$start += (($year - 1970) * 365 +
+		   int (($year - 1968) / 4)) * 3600 * 24;
+	$year += $inc;
+	$end += (($year - 1970) * 365 + int (($year - 1968) / 4)) * 3600 * 24;
+	$in{$start} = $type_in ? &kb2i($res[$value_in - 1])
+	                       : $res[$value_in - 1];
+	$out{$start} = $type_out ? &kb2i($res[$value_out - 1])
+	                         : $res[$value_out - 1];
+	$dates{$start} = $end;
+      }
+      my ($xmax, $ymax) = (500, 170);
+      &Chrono ("$IMG_dir/$filename", $title, $color_bg, $xmax, $ymax,
+	       \%in, \%out, \%dates, $label_in, $label_out,
+	       $color_in, $color_out, $unit);
+      $result .= "<IMG WIDTH=\"$xmax\" HEIGHT=\"$ymax\" ";
+      $result .= "SRC=\"$IMG_pth$filename\" ALT=\"Graph\">\n";
+      $i++;
+    }
+    $result .= "<P>\n";
+  }
+  $i = 0;
+  $result .= "<TABLE BORDER=\"1\"><TR>";
+  my $temp = '';
+  while (defined ${${$output{'index'}{'column'}}[$i]}{'title'}) {
+    my $title = &GetValue (${${$output{'index'}{'column'}}[$i]}{'title'});
+    my $name = '';
+    $name = &GetValue (${${$output{'index'}{'column'}}[$i]}{'name'})
+      if defined ${${$output{'index'}{'column'}}[$i]}{'name'};
+    my @list = split /\|/, $name;
+    if ($name) {
+      $result .= sprintf "<TH COLSPAN=%d>$title</TH>", $#list + 1;
+    }
+    else {
+      $result .= "<TH ROWSPAN=\"2\">$title</TH>";
+    }
+    foreach (@list) {
+      $temp .= "<TH>$_</TH>";
+    }
+    $i++;
+  }
+  $result .= "</TR>\n<TR>$temp</TR>\n";
+
+  $i = 0;
+  foreach (sort {$b cmp $a} (keys %data)) {
+    if ($CYCLE == 0 || $i < $CYCLE) {
+      my @list = split /\|/, $data{$_};
+      my $str = "<TR><TD ALIGN=LEFT>";
+      $str .= "<A HREF=\"$_\">" if -e "$rep/$_";
+      $str .= shift @list;
+      $str .= "</A>" if -e "$rep/$_";;
+      $str .= "</TD>";
+      while (@list) {
+	$str .= "<TD ALIGN=RIGHT>";
+	my $t = shift @list;
+	$t =~ s/^\0+//o; # remove garbage, if any.
+	$str .= "$t</TD>";
+      }
+      $str .= "</TR>\n";
+      $result .= "$str";
+    }
+    $i++;
+  }
+  $result .= "</TABLE>\n</CENTER>\n<P><HR>";
+  $result .= "<A HREF=\"ftp://ftp.sofaraway.org/pub/innreport/\">innreport";
+  $result .= "</A> $version (c) 1996-1999 ";
+  $result .= "by Fabien Tassin &lt;<A HREF=\"mailto:fta\@sofaraway.org\">";
+  $result .= "fta\@sofaraway.org</A>&gt;.\n";
+  if (defined ($output{'default'}{'footer'})) {
+    my ($t) = $output{'default'}{'footer'} =~ m/^\"\s*(.*?)\s*\"$/o;
+    $t =~ s/\\\"/\"/go;
+    $result .= "<BR>" . $t;
+  }
+  $result .= "$HTML_footer\n</BODY>\n</HTML>\n";
+  my $name = $rep . "/" . $index;
+  while ($name =~ m/\/\.\.\//o) {
+    $name =~ s|^\./||o;                 # ^./xxx        =>      ^xxx
+    $name =~ s|/\./|/|go;               # xxx/./yyy     =>      xxx/yyy
+    $name =~ s|/+|/|go;                 # xxx//yyy      =>      xxx/yyy
+    $name =~ s|^/\.\./|/|o;             # ^/../xxx      =>      ^/xxx
+    $name =~ s|^[^/]+/\.\./||o;         # ^xxx/../      =>      ^nothing
+    $name =~ s|/[^/]+/\.\./|/|go;       # /yyy/../      =>      /
+  }
+
+  open (INDEX, "> $name") || die "Error: Unable to create $name\n";
+  print INDEX $result;
+  close INDEX;
+  1;
+}
+
+sub Graph3d {
+  my $filename = shift;           # filename
+  my $title = shift;              # title
+  my $xmax = shift;               # width
+  my $n = shift;                  # Number of hash code tables
+
+  no strict;
+  my ($i, $k, $t);
+  my @val;
+  for $i (0 .. $n - 1) {
+    push @val, shift;           # hash code table
+  }
+  my $colors = shift;             # colors table
+  my $labels = shift;             # labels
+
+  my $max = 0;
+  my $max_size = 0;
+  my $size = 0;
+  foreach $k (sort keys (%{$val[0]})) {
+    $t = 0;
+    $size++;
+    for $i (0 .. $n - 1) {
+      $t += ${$val[$i]}{$k} if defined ${$val[$i]}{$k};
+    }
+    $max = $t if $max < $t;
+    $t = length "$k";
+    $max_size = $t if $max_size < $t;
+  }
+  $max = 1 unless $max;
+  $max_size *= gdSmallFont->width;
+
+  # relief
+  my ($rx, $ry) = (15, 5);
+
+  # margins
+  my ($mt, $mb) = (40, 40);
+  my $ml = $max_size > 30 ? $max_size + 8 : 30;
+
+  my $mr = 7 + (length "$max") * gdSmallFont->width;
+  $mr = 30 if $mr < 30;
+
+  # height of each bar
+  my $h = 12;
+
+  # difference between 2 bars
+  my $d = 25;
+
+  my $ymax = $size * $d + $mt + $mb;
+  my $image = new GD::Image ($xmax, $ymax);
+
+  my ($white, $black);
+  if (defined $output{'default'}{'graph_fg'}) {
+    my $t = $output{'default'}{'graph_fg'};
+    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+    $t =~ m/^[\da-fA-F]{6}$/o ||
+      die "Error in section 'default' section 'graph_fg'. Bad color.\n";
+    my @c = map { hex ($_) } ($t =~ m/^(..)(..)(..)$/);
+    $black = $image->colorAllocate (@c);
+  }
+  else {
+    $black = $image->colorAllocate (  0,   0,   0);
+  }
+  if (defined $output{'default'}{'graph_bg'}) {
+    my $t = $output{'default'}{'graph_bg'};
+    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+    $t =~ m/^[\da-fA-F]{6}$/o ||
+      die "Error in section 'default' section 'graph_bg'. Bad color.\n";
+    my @c = map { hex ($_) } ($t =~ m/^(..)(..)(..)$/);
+    $white = $image->colorAllocate (@c);
+  }
+  else {
+    $white = $image->colorAllocate (255, 255, 255);
+  }
+  $image->filledRectangle (0, 0, $xmax, $ymax, $white);
+  my @col;
+  for $i (0 .. $n - 1) {
+    $col[$i][0] = $image->colorAllocate
+      ($$colors[$i][0], $$colors[$i][1], $$colors[$i][2]);
+    $col[$i][1] = $image->colorAllocate
+      ($$colors[$i][0] * 3 / 4, $$colors[$i][1] * 3 / 4,
+       $$colors[$i][2] * 3 / 4);
+    $col[$i][2] = $image->colorAllocate
+      ($$colors[$i][0] * 2 / 3, $$colors[$i][1] * 2 / 3,
+       $$colors[$i][2] * 2 / 3);
+  }
+
+  $image->transparent ($white) if $transparent;
+
+  $image->rectangle (0, 0, $xmax - 1, $size * $d + $mt + $mb - 1, $black);
+  $image->line (0, $mt - 5, $xmax - 1, $mt - 5, $black);
+  for $i (0 .. $n - 1) {
+    $image->string (gdSmallFont, $i * $xmax / $n + $mt - 10 + $rx,
+		    ($mt - gdSmallFont->height) / 2, "$$labels[$i]", $black);
+    $image->filledRectangle ($i * $xmax / $n + 10, 8 + $ry / 2,
+		       $i * $xmax / $n + $mt - 10, $mt - 12, $col[$i][0]);
+    $image->rectangle ($i * $xmax / $n + 10, 8 + $ry / 2,
+		       $i * $xmax / $n + $mt - 10, $mt - 12, $black);
+    {
+      my $poly = new GD::Polygon;
+      $poly->addPt($i * $xmax / $n + 10, 8 + $ry / 2);
+      $poly->addPt($i * $xmax / $n + 10 + $rx / 2, 8);
+      $poly->addPt($i * $xmax / $n + $mt - 10 + $rx / 2, 8);
+      $poly->addPt($i * $xmax / $n + $mt - 10, 8 + $ry / 2);
+
+      $image->filledPolygon($poly, $col[$i][1]);
+      $image->polygon($poly, $black);
+    }
+    {
+      my $poly = new GD::Polygon;
+      $poly->addPt($i * $xmax / $n + $mt - 10 + $rx / 2, 8);
+      $poly->addPt($i * $xmax / $n + $mt - 10, 8 + $ry / 2);
+      $poly->addPt($i * $xmax / $n + $mt - 10, $mt - 12);
+      $poly->addPt($i * $xmax / $n + $mt - 10 + $rx / 2, $mt - 12 - $ry / 2);
+
+      $image->filledPolygon($poly, $col[$i][2]);
+      $image->polygon($poly, $black);
+    }
+  }
+  # Title
+  $image->string (gdMediumBoldFont, ($xmax - gdMediumBoldFont->width *
+		  (length "$title")) / 2, $ymax - gdMediumBoldFont->height - 7,
+		  "$title", $black);
+
+  my $e = $mt - $h + $d;
+  my $r = ($xmax - $ml - $mr - $rx) / $max;
+
+  # Axe Oz
+  $image->line ($ml + $rx, $mt, $ml + $rx, $size * $d + $mt - $ry, $black);
+  $image->line ($ml + $rx + $max * $r, $mt, $ml + $rx + $max * $r,
+		$size * $d + $mt - $ry, $black);
+  $image->line ($ml, $mt + $ry, $ml, $size * $d + $mt, $black);
+  # Axe Ox
+  $image->line ($ml + $rx, $size * $d + $mt - $ry,
+		$ml + $rx - 2 * $rx, $size * $d + $mt + $ry, $black);
+  # Axe Oy
+  $image->line ($ml + $rx, $size * $d + $mt - $ry,
+		$xmax - $mr / 2, $size * $d + $mt - $ry, $black);
+  $image->line ($ml, $size * $d + $mt,
+		$xmax - $mr - $rx, $size * $d + $mt, $black);
+
+  # Graduations..
+  my $nn = 10;
+  for $k (1 .. ($nn - 1)) {
+    $image->dashedLine ($ml + $rx + $k * ($xmax - $ml - $mr - $rx) / $nn,
+		  $mt + 10, $ml + $rx + $k * ($xmax - $ml - $mr - $rx) / $nn,
+		  $size * $d + $mt - $ry, $black);
+    $image->dashedLine ($ml + $rx + $k * ($xmax - $ml - $mr - $rx) / $nn,
+			$size * $d + $mt - $ry,
+			$ml + $k * ($xmax - $ml - $mr - $rx) / $nn,
+			$size * $d + $mt, $black);
+    $image->line ($ml + $k * ($xmax - $ml - $mr - $rx) / $nn,
+			$size * $d + $mt,
+			$ml + $k * ($xmax - $ml - $mr - $rx) / $nn,
+			$size * $d + $mt + 5, $black);
+    my $t = sprintf "%d%%", $k * 10;
+    $image->string (gdSmallFont, $ml + $k * ($xmax - $ml - $mr - $rx) / $nn -
+		    (length "$t") * gdSmallFont->width / 2,
+		    $size * $d + $mt + 6, "$t", $black);
+  }
+  {
+    my $t = sprintf "%d%%", 0;
+    $image->line ($ml, $size * $d + $mt, $ml, $size * $d + $mt + 5, $black);
+    $image->string (gdSmallFont, $ml - (length "$t") * gdSmallFont->width / 2,
+		    $size * $d + $mt + 6, "$t", $black);
+    $image->line ($xmax - $mr, $size * $d + $mt - $ry,
+		  $xmax - $mr - $rx, $size * $d + $mt, $black);
+    $image->line ($xmax - $mr - $rx, $size * $d + $mt,
+		  $xmax - $mr - $rx, $size * $d + $mt + 5, $black);
+    $t = sprintf "%d%%", 100;
+    $image->string (gdSmallFont, $xmax - $mr - $rx
+		    - (length "$t") * gdSmallFont->width / 2,
+		    $size * $d + $mt + 6, "$t", $black);
+  }
+  foreach $k (sort {${$val[0]}{$b} <=> ${$val[0]}{$a}} keys (%{$val[0]})) {
+    $image->string (gdSmallFont, $ml - (length "$k") * gdSmallFont->width - 3,
+                    $e + $h / 2 - gdSmallFont->height / 2, "$k", $black);
+    my $t = 0;
+    $image->line ($ml + ($t + ${$val[0]}{$k}) * $r + $rx - $rx, $e + $h,
+                  $ml + ($t + ${$val[0]}{$k}) * $r + $rx, $e - $ry + $h,
+                  $black);
+    for $i (0 .. $n - 1) {
+      next unless defined ${$val[$i]}{$k};
+      {
+	my $poly = new GD::Polygon;
+	$poly->addPt($ml + $t * $r, $e);
+	$poly->addPt($ml + $t * $r + $rx, $e - $ry);
+	$poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx, $e - $ry);
+        $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r, $e);
+
+        $image->filledPolygon($poly, $col[$i][1]);
+        $image->polygon($poly, $black);
+      }
+      unless (${$val[$i + 1]}{$k} || ${$val[$i]}{$k} == 0) {
+	my $poly = new GD::Polygon;
+	$poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx, $e - $ry);
+	$poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx - $rx, $e);
+	$poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx - $rx, $e + $h);
+	$poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx, $e - $ry + $h);
+
+	$image->filledPolygon($poly, $col[$i][2]);
+	$image->polygon($poly, $black);
+      }
+      $image->filledRectangle ($ml + $t * $r, $e,
+			       $ml + ($t + ${$val[$i]}{$k}) * $r, $e + $h,
+                               $col[$i][0]);
+      $image->rectangle ($ml + $t * $r, $e, $ml + ($t + ${$val[$i]}{$k}) * $r,
+                         $e + $h, $black);
+      $t += ${$val[$i]}{$k};
+    }
+    # total length (offered)
+    $image->filledRectangle ($ml + $t * $r + $rx + 3,
+			     $e - 2 - gdSmallFont->height / 2,
+			     $ml + $t * $r + $rx + 4 +
+			     gdSmallFont->width * length $t,
+			     $e - 6 + gdSmallFont->height / 2, $white);
+    $image->string (gdSmallFont, $ml + $t * $r + $rx + 5,
+		    $e - 3 - gdSmallFont->height / 2, "$t", $black);
+    # first value (accepted)
+    $image->filledRectangle ($ml + $t * $r + $rx + 3,
+			     $e - 4 + gdSmallFont->height / 2,
+			     $ml + $t * $r + $rx + 4 +
+			     gdSmallFont->width * length "${$val[0]}{$k}",
+			     $e - 2 + gdSmallFont->height, $white);
+    $image->string (gdSmallFont, $ml + $t * $r + $rx + 5,
+		    $e - 5 + gdSmallFont->height / 2, ${$val[0]}{$k}, $black);
+    $e += $d;
+  }
+  open (IMG, "> $filename") || die "Error: Can't open \"$filename\": $!\n";
+  if ($GD_FORMAT eq 'png') {
+    print IMG $image->png;
+  }
+  else {
+    print IMG $image->gif;
+  }
+  close IMG;
+  $ymax;
+}
+
+sub Histo {
+  my ($filename, $title, $xmax, $factor,
+      $labelx, $labely, $val1, $labels1) = @_;
+
+  no strict;
+  my $max = 0;
+  my $ymax = 300;
+  my $nb = 0;
+  # A hugly hack to convert hashes to lists..
+  # and to adjust the first and the last value...
+  # this function should be rewritten..
+  my (@a, @b, $kk);
+  foreach $kk (sort keys (%$val1))   {
+    if (defined $$val1{$kk}) {
+      $nb++;
+      # Arg... the following MUST be removed !!!!!!!!!
+      $$val1{$kk} = $$val1{$kk} / $innreport_inn::inn_flow_time{$kk} * 3600
+	if ($innreport_inn::inn_flow_time{$kk} != 3600) &&
+	   ($innreport_inn::inn_flow_time{$kk} != 0);
+      push @a, $$val1{$kk};
+      $max = $$val1{$kk} if $$val1{$kk} > $max;
+      push @b, $$labels1{$kk};
+    }
+  }
+  return 0 unless $nb; # strange, no data.
+  my $val = \@a;
+  my $labels = \@b;
+  my ($i, $j);
+  my ($marginl, $marginr, $margint, $marginb, $shx, $shy);
+
+  my $image = new GD::Image($xmax, $ymax);
+  my ($white, $black);
+  if (defined $output{'default'}{'graph_fg'}) {
+    my $t = $output{'default'}{'graph_fg'};
+    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+    $t =~ m/^[\da-fA-F]{6}$/o ||
+      die "Error in section 'default' section 'graph_fg'. Bad color.\n";
+    my @c = map { hex ($_) } ($t =~ m/^(..)(..)(..)$/);
+    $black = $image->colorAllocate (@c);
+  }
+  else {
+    $black = $image->colorAllocate (  0,   0,   0);
+  }
+  if (defined $output{'default'}{'graph_bg'}) {
+    my $t = $output{'default'}{'graph_bg'};
+    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+    $t =~ m/^[\da-fA-F]{6}$/o ||
+      die "Error in section 'default' section 'graph_bg'. Bad color.\n";
+    my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+    $white = $image->colorAllocate (@c);
+  }
+  else {
+    $white = $image->colorAllocate (255, 255, 255);
+  }
+  $image->filledRectangle (0, 0, $xmax, $ymax, $white);
+  my $gray  = $image->colorAllocate (128, 128, 128);
+  my $red   = $image->colorAllocate (255,   0,   0);
+  my $red2  = $image->colorAllocate (189,   0,   0);
+  my $red3  = $image->colorAllocate (127,   0,   0);
+  my $coltxt = $black;
+
+  $image->transparent ($white) if $transparent;
+
+  my $FontWidth = gdSmallFont->width;
+  my $FontHeight = gdSmallFont->height;
+
+  $marginl = 60;
+  $marginr = 30;
+  $margint = 60;
+  $marginb = 30;
+  $shx = 7;
+  $shy = 7;
+
+  $max = 1 unless $max;
+  my $part = 8;
+  $max /= $factor;
+
+  my $old_max = $max;
+  {
+    my $t = log ($max) / log 10;
+    $t = sprintf "%.0f", $t - 1;
+    $t = exp ($t * log 10);
+    $max = sprintf "%.0f", $max / $t * 10 + 0.4;
+    my $t2 = sprintf "%.0f", $max / $part;
+    unless ($part * $t2 == $max) {
+      while ($part * $t2 != $max) {
+	$max++;
+	$t2 = sprintf "%d", $max / $part;
+      }
+    }
+    $max = $max * $t / 10;
+  }
+
+  # Title
+  $image->string (gdMediumBoldFont,
+		  ($xmax - length ($title) * gdMediumBoldFont->width) / 2,
+		  ($margint - $shy - gdMediumBoldFont->height) / 2,
+		  $title, $coltxt);
+
+  # Labels
+  $image->string (gdSmallFont, $marginl / 2, $margint / 2, $labely, $coltxt);
+  $image->string (gdSmallFont, $xmax - $marginr / 2 -
+		  $FontWidth * length ($labelx), $ymax - $marginb / 2,
+		  $labelx, $coltxt);
+
+  # Max
+  $image->line ($marginl, $ymax - $marginb - $shy -
+		$old_max * ($ymax - $marginb - $margint - $shy) / $max,
+		$xmax - $marginr, $ymax - $marginb - $shy -
+		$old_max * ($ymax - $marginb - $margint - $shy) / $max, $red);
+  $image->line ($marginl, $ymax - $marginb - $shy -
+		$old_max * ($ymax - $marginb - $margint - $shy) / $max,
+		$marginl - $shx, $ymax - $marginb -
+		$old_max * ($ymax - $marginb - $margint - $shy) / $max, $red);
+
+  # Left
+  $image->line ($marginl - $shx, $margint + $shy,
+		$marginl - $shx, $ymax - $marginb, $coltxt);
+  $image->line ($marginl, $margint,
+		$marginl, $ymax - $marginb - $shy, $coltxt);
+  $image->line ($marginl, $margint,
+		$marginl - $shx, $margint + $shy, $coltxt);
+  $image->line ($marginl - $shx, $ymax - $marginb,
+		$marginl, $ymax - $marginb - $shy, $coltxt);
+
+  # Right
+  $image->line ($xmax - $marginr, $margint,
+		$xmax - $marginr, $ymax - $marginb - $shy, $coltxt);
+  $image->line ($xmax - $marginr - $shx, $ymax - $marginb,
+		$xmax - $marginr, $ymax - $marginb - $shy, $coltxt);
+
+  # Bottom
+  $image->line ($marginl - $shx, $ymax - $marginb,
+		$xmax - $marginr - $shx, $ymax - $marginb, $coltxt);
+  $image->line ($marginl, $ymax - $marginb - $shy,
+		$xmax - $marginr, $ymax - $marginb - $shy, $coltxt);
+  $image->fill ($xmax / 2, $ymax - $marginb - $shy / 2, $gray);
+
+  # Top
+  $image->line ($marginl, $margint,
+		$xmax - $marginr, $margint, $coltxt);
+  $image->setStyle ($coltxt, $coltxt, &GD::gdTransparent,
+		    &GD::gdTransparent, &GD::gdTransparent);
+  # Graduations
+  for ($i = 0; $i <= $part; $i++) {
+    $j = $max * $i / $part ;  # Warning to floor
+    # $j = ($max / $part) * ($i / 10000);
+    # $j *= 10000;
+
+    # Little hack...
+    $j = sprintf "%d", $j if $j > 100;
+
+    $image->line ($marginl - $shx - 3, $ymax - $marginb -
+		  $i * ($ymax - $marginb - $margint - $shy) / $part,
+		  $marginl - $shx, $ymax - $marginb -
+		  $i * ($ymax - $marginb - $margint - $shy) / $part, $coltxt);
+    $image->line ($marginl - $shx, $ymax - $marginb -
+		  $i * ($ymax - $marginb - $margint - $shy) / $part,
+		  $marginl, $ymax - $marginb - $shy -
+		  $i * ($ymax - $marginb - $margint - $shy) / $part, gdStyled);
+    $image->line ($marginl, $ymax - $marginb - $shy -
+		  $i * ($ymax - $marginb - $margint - $shy) / $part,
+		  $xmax - $marginr, $ymax - $marginb - $shy -
+		  $i * ($ymax - $marginb - $margint - $shy) / $part, gdStyled);
+    $image->string (gdSmallFont,
+		    $marginl - $shx - $FontWidth * length ("$j") - 7,
+		    $ymax - $marginb -
+		    ($i) * ($ymax - $marginb - $margint - $shy) / ($part) -
+		    $FontHeight / 2, "$j", $coltxt);
+  }
+
+  # Graduation (right bottom corner)
+  $image->line ($xmax - $marginr - $shx, $ymax - $marginb,
+		$xmax - $marginr - $shx, $ymax - $marginb + 3, $coltxt);
+  # Bars
+  $i = 0;
+  my $w = ($xmax - $marginl - $marginr) / $nb;
+  my $k = $w / 5;
+  $$val[$nb - 1] = 0 unless $$val[$nb - 1];
+  foreach $j (@$val) {
+    my $MAX = 1;
+    if ($i++ <= $nb) {
+      # Graduation
+      $image->line ($marginl + ($i - 1) * $w - $shx, $ymax - $marginb,
+		    $marginl + ($i - 1) * $w - $shx, $ymax - $marginb + 3,
+		    $coltxt);
+      my $ii = sprintf "%d", $i / $MAX;
+      $image->string (gdSmallFont,
+		      $marginl + ($i - 0.5) * $w + 1 -
+		      ($FontWidth * length ($$labels[$i-1])) / 2 - $shx,
+		      $ymax - $marginb + 3, $$labels[$i-1], $coltxt)
+	unless ($w < $FontWidth * length ($$labels[$i-1]))
+		&& ($i != $MAX * $ii);
+
+      # Right
+      my $poly = new GD::Polygon;
+      $poly->addPt($marginl + ($i) * $w - $k, $ymax - $marginb - $shy -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+      $poly->addPt($marginl + ($i) * $w - $k, $ymax - $marginb - $shy);
+      $poly->addPt($marginl + ($i) * $w - $k - $shx, $ymax - $marginb);
+      $poly->addPt($marginl + ($i) * $w - $k - $shx, $ymax - $marginb -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+
+      $image->filledPolygon($poly, $red3);
+      $image->polygon($poly, $coltxt);
+
+      # Front
+      $image->filledRectangle ($marginl + ($i - 1) * $w + $k - $shx,
+		   $ymax - $marginb -
+		   $j  / $factor * ($ymax - $marginb - $margint - $shy) / $max,
+		   $marginl + ($i) * $w - $k - $shx,
+		   $ymax - $marginb, $red);
+      $image->rectangle ($marginl + ($i - 1) * $w + $k - $shx,
+		   $ymax - $marginb -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max,
+		   $marginl + ($i) * $w - $k - $shx,
+		   $ymax - $marginb, $coltxt);
+      # Top
+      my $poly2 = new GD::Polygon;
+      $poly2->addPt($marginl + ($i - 1) * $w + $k, $ymax - $marginb - $shy -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+      $poly2->addPt($marginl + ($i) * $w - $k, $ymax - $marginb - $shy -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+      $poly2->addPt($marginl + ($i) * $w - $k - $shx, $ymax - $marginb -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+      $poly2->addPt($marginl + ($i - 1) * $w + $k - $shx, $ymax - $marginb -
+		   $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+
+      $image->rectangle (0, 0, $xmax - 1, $ymax - 1, $coltxt);
+      $image->filledPolygon($poly2, $red2);
+      $image->polygon($poly2, $coltxt);
+    }
+  }
+
+  open (IMG, "> $filename") || die "Can't create '$filename'\n";
+  if ($GD_FORMAT eq 'png') {
+    print IMG $image->png;
+  }
+  else {
+    print IMG $image->gif;
+  }
+  close IMG;
+  1;
+}
+
+sub Chrono {
+  my $filename = shift;           # filename
+  my $title = shift;              # title
+  my $color_bg = shift;           # background color
+  my $xmax = shift;               # width
+  my $ymax = shift;               # height
+
+  my $in = shift;
+  my $out = shift;
+  my $dates = shift;
+
+  my $legend_in = shift;
+  my $legend_out = shift;
+
+  my $color_in = shift;
+  my $color_out = shift;
+
+  my $unit = shift;
+
+  my $key;
+  my $x_min = 1E30;
+  my $x_max = 0;
+  my $y_min = 0;
+  my $y_max;
+  my $y_max_in = 0;
+  my $y_max_out = 0;
+
+  foreach $key (sort keys %$dates) {
+    $x_min = $key if $x_min > $key;
+    $x_max = $$dates{$key} if $x_max < $$dates{$key};
+    my $t = $$out{$key} / ($$dates{$key} - $key);
+    $y_max_out = $t if $y_max_out < $t;
+    $t = $$in{$key} / ($$dates{$key} - $key);
+    $y_max_in = $t if $y_max_in < $t;
+  }
+  $y_max = $y_max_out > $y_max_in ? $y_max_out : $y_max_in;
+  my $factor = 1;
+  if ($y_max < 1) {
+    $factor = 60;
+    if ($y_max < 4 / 60) {
+      $y_max = 4 / 60;
+    }
+    else {
+      $y_max = int ($y_max * $factor) + 1;
+      $y_max += (4 - ($y_max % 4)) % 4;
+      $y_max /= $factor;
+    }
+  }
+  else {
+    $y_max = int ($y_max) + 1;
+    $y_max += (4 - ($y_max % 4)) % 4;
+  }
+
+  $unit .= "/" . ($factor == 60 ? "min" : "sec");
+
+  # min range is 4 weeks.
+  my $delta = $x_max - $x_min;
+  $x_min = $x_max - 3024000 if $delta < 3024000;
+  # between 4 weeks and one year, range is a year.
+  $x_min = $x_max - 31536000 if ($delta < 31536000 && $delta > 3024000);
+  # max range is 13 months
+  $x_min = $x_max - 34128000 if $delta > 34128000;
+  my $image = new GD::Image ($xmax, $ymax);
+  my ($white, $black);
+  if (defined $output{'default'}{'graph_fg'}) {
+    my $t = $output{'default'}{'graph_fg'};
+    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+    $t =~ m/^[\da-fA-F]{6}$/o ||
+      die "Error in section 'default' section 'graph_fg'. Bad color.\n";
+    my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+    $black = $image->colorAllocate (@c);
+  }
+  else {
+    $black = $image->colorAllocate (  0,   0,   0);
+  }
+  if (defined $output{'default'}{'graph_bg'}) {
+    my $t = $output{'default'}{'graph_bg'};
+    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+    $t =~ m/^[\da-fA-F]{6}$/o ||
+      die "Error in section 'default' section 'graph_bg'. Bad color.\n";
+    my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+    $white = $image->colorAllocate (@c);
+  }
+  else {
+    $white = $image->colorAllocate (255, 255, 255);
+  }
+  my $bg;
+  if (defined $color_bg) {
+    $color_bg =~ m/^\#[\da-fA-F]{6}$/o ||
+      die "Error in section 'index'. Bad color $color_bg.\n";
+    my @c = map { hex $_ } ($color_bg =~ m/^\#(..)(..)(..)$/);
+    $bg = $image->colorAllocate (@c);
+  }
+  else {
+    $bg = $image->colorAllocate (255, 255, 206);
+  }
+  my $col_in;
+  if (defined $color_in) {
+    $color_in =~ m/^\#[\da-fA-F]{6}$/o ||
+      die "Error in section 'index'. Bad color $color_in.\n";
+    my @c = map { hex $_ } ($color_in =~ m/^\#(..)(..)(..)$/);
+    $col_in = $image->colorAllocate (@c);
+  }
+  else {
+    $col_in = $image->colorAllocate ( 80, 159, 207);
+  }
+  my $col_out;
+  my @col_out = (  0,   0, 255);
+  if (defined $color_out) {
+    $color_out =~ m/^\#[\da-fA-F]{6}$/o ||
+      die "Error in section 'index'. Bad color $color_out.\n";
+    my @c = map { hex $_ } ($color_out =~ m/^\#(..)(..)(..)$/);
+    $col_out = $image->colorAllocate (@c);
+    @col_out = @c;
+  }
+  else {
+    $col_out = $image->colorAllocate (@col_out);
+  }
+
+  my $white2  = $image->colorAllocate (255, 255, 255);
+  my $gray    = $image->colorAllocate (192, 192, 192);
+  my $red     = $image->colorAllocate (255,   0,   0);
+  my $coltxt  = $black;
+
+  my $size    = 22; # legend
+  # legend statistics
+  my ($max_in, $max_out) = (0, 0);         # min
+  my ($min_in, $min_out) = (1E10, 1E10);   # max
+  my ($t_in, $t_out) = (0, 0);             # time
+  my ($s_in, $s_out) = (0, 0);             # sum
+
+  $image->filledRectangle (0, 0, $xmax, $ymax, $gray);
+  $image->transparent ($gray) if $transparent;
+
+  my $FontWidth = gdSmallFont->width;
+  my $FontHeight = gdSmallFont->height;
+  $image->setStyle ($black, &GD::gdTransparent, &GD::gdTransparent);
+
+  my $marginl = 13 + $FontWidth * length (sprintf "%d", $y_max * $factor);
+  my $marginr = 15 + 4 * $FontWidth; # "100%"
+  my $margint = 2 * $FontHeight + gdMediumBoldFont->height;
+  my $marginb = 2 * $FontHeight + $size;
+  my $xratio = ($xmax - $marginl - $marginr) / ($x_max - $x_min);
+  my $yratio = ($ymax - $margint - $marginb) / ($y_max - $y_min);
+
+  my $frame = new GD::Polygon;
+  $frame->addPt(2, $margint - $FontHeight -3);
+  $frame->addPt($xmax - 2, $margint - $FontHeight -3);
+  $frame->addPt($xmax - 2, $ymax - 3);
+  $frame->addPt(2, $ymax - 3);
+  $image->filledPolygon($frame, $white2);
+  $image->polygon($frame, $black);
+
+  $image->filledRectangle ($marginl, $margint,
+			   $xmax - $marginr, $ymax - $marginb, $bg);
+  my $brush = new GD::Image(1, 2);
+  my $b_col = $brush->colorAllocate(@col_out);
+  $brush->line(0, 0, 0, 1, $b_col);
+  $image->setBrush($brush);
+  my ($old_x, $old_y_in, $old_y_out);
+  foreach $key (sort keys %$dates) {
+    next if $key < $x_min;
+    my $delta = $$dates{$key} - $key;
+    $min_in  = $$in{$key} / $delta  if $min_in  > $$in{$key} / $delta;
+    $max_in  = $$in{$key} / $delta  if $max_in  < $$in{$key} / $delta;
+    $min_out = $$out{$key} / $delta if $min_out > $$out{$key} / $delta;
+    $max_out = $$out{$key} / $delta if $max_out < $$out{$key} / $delta;
+    $t_in   += $delta;
+    $s_in   += $$in{$key};
+    $s_out  += $$out{$key};
+
+    my $tt_in  = $$in{$key} / ($$dates{$key} - $key) * $yratio;
+    my $tt_out = $$out{$key} / ($$dates{$key} - $key) * $yratio;
+    my $new_x = $marginl + ($key - $x_min) * $xratio;
+    $image->filledRectangle ($marginl + ($key - $x_min) * $xratio,
+		       $ymax - $marginb - $tt_in,
+		       $marginl + ($$dates{$key} - $x_min) * $xratio,
+		       $ymax - $marginb, $col_in);
+    if (defined $old_x) {
+      $old_x = $new_x if $old_x > $new_x;
+      my $poly = new GD::Polygon;
+      $poly->addPt($old_x, $old_y_in);
+      $poly->addPt($new_x, $ymax - $marginb - $tt_in);
+      $poly->addPt($new_x, $ymax - $marginb);
+      $poly->addPt($old_x, $ymax - $marginb);
+      $image->filledPolygon($poly, $col_in);
+    }
+    $image->line ($marginl + ($key - $x_min) * $xratio,
+		       $ymax - $marginb - $tt_out,
+		       $marginl + ($$dates{$key} - $x_min) * $xratio,
+		       $ymax - $marginb - $tt_out, &GD::gdBrushed);
+    $image->line ($old_x, $old_y_out, $new_x,
+		  $ymax - $marginb - $tt_out, $col_out) if defined $old_x;
+    $old_x = $marginl + ($$dates{$key} - $x_min) * $xratio;
+    $old_y_in  = $ymax - $marginb - $tt_in;
+    $old_y_out = $ymax - $marginb - $tt_out;
+  }
+  $t_out = $t_in;
+
+  # main frame
+  $image->rectangle ($marginl, $margint,
+		     $xmax - $marginr, $ymax - $marginb, $black);
+  # graduations
+  my $i;
+  foreach $i (0, 25, 50, 75, 100) {
+    my $t = $ymax - $margint - $marginb;
+    $image->line ($marginl, $ymax - $marginb - $i / 100 * $t,
+		  $xmax - $marginr, $ymax - $marginb - $i / 100 * $t,
+		  &GD::gdStyled);
+    $image->line ($xmax - $marginr, $ymax - $marginb - $i / 100 * $t,
+		  $xmax - $marginr + 3, $ymax - $marginb - $i / 100 * $t,
+		  $black);
+    $image->line ($marginl - 3, $ymax - $marginb - $i / 100 * $t,
+		  $marginl, $ymax - $marginb - $i / 100 * $t,
+		  $black);
+    $image->string (&GD::gdSmallFont, $xmax - $marginr + 8, - $FontHeight / 2 +
+		    $ymax - $marginb - $i / 100 * $t, "$i%", $black);
+    my $s = sprintf "%d", $y_max * $i / 100 * $factor;
+    $image->string (&GD::gdSmallFont, $marginl - 5 - $FontWidth * length $s,
+		    - $FontHeight / 2 +
+		    $ymax - $marginb - $i / 100 * $t, $s, $black);
+  }
+  ##
+  my $w = 604800;      # number of seconds in a week
+  my $y = 31536000;    # number of seconds in a 365 days year
+  my $mm = 2592000;    # number of seconds in a 30 days month
+  if ($x_max - $x_min <= 3024000) { # less than five weeks
+    # unit is a week
+    # 1/1/1990 is a monday. Use this as a basis.
+    my $d = 631152000; # number of seconds between 1/1/1970 and 1/1/1990
+    my $n = int ($x_min / $y);
+    my $t = $x_min - $n * $y - int (($n - 2) / 4) * 24 * 3600;
+    my $f = int ($t / $w);
+    $n = $d + int (($x_min - $d) / $w) * $w;
+    while ($n < $x_max) {
+      $t = $marginl + ($n - $x_min) * $xratio;
+      if ($n > $x_min) {
+	$image->line ($t, $margint, $t, $ymax - $marginb, &GD::gdStyled);
+	$image->line ($t, $ymax - $marginb, $t, $ymax - $marginb + 2, $black);
+      }
+      $image->string (&GD::gdSmallFont, $FontWidth * 7 / 2 + $t,
+		      $ymax - $marginb + 4, (sprintf "Week %02d", $f), $black)
+	if ($n + $w / 2 > $x_min) && ($n + $w / 2 < $x_max);
+      $f++;
+      $n += $w;
+      $t = int ($n / $y);
+      $f = 0
+	if $n - $y * $t - int (($t - 2) / 4) * 24 * 3600 < $w && $f > 50;
+    }
+    $d = 86400;    # 1 day
+    $n = int ($x_min / $y);
+    $t = $n * $y + int (($n - 2) / 4) * 24 * 3600;
+    $i = 0;
+    my $x;
+    while ($t < $x_max) {
+      $x = $marginl + ($t - $x_min) * $xratio;
+      $image->line ($x, $margint, $x, $ymax - $marginb + 2, $red)
+	if $t > $x_min;
+      $t += $mm;
+      $t += $d if $i == 0 || $i == 2 || $i == 4 ||
+		  $i == 6 || $i == 7 || $i == 9 || $i == 11; # 31 days months
+      if ($i == 1) {  # february ?
+	$t -= 2 * $d;
+	$t += $d unless (1970 + int ($t / $y)) % 4;
+      }
+      $i++;
+      $i = 0 if $i == 12; # Happy New Year !!
+    }
+  }
+  else {
+    # unit is a month
+    my $n = int ($x_min / $y);
+    my $t = $n * $y + int (($n - 2) / 4) * 24 * 3600;
+    my @m = ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+    my $d = 86400;    # 1 day
+    my $i = 0;
+    my $x;
+    while ($t < $x_max) {
+      $x = $marginl + ($t - $x_min) * $xratio;
+      if ($t > $x_min) {
+	$image->line ($x, $margint, $x, $ymax - $marginb, &GD::gdStyled);
+	$image->line ($x, $ymax - $marginb, $x,
+		      $ymax - $marginb + 2, $black);
+	$image->line ($x, $margint, $x, $ymax - $marginb, $red) unless $i;
+      }
+      $image->string (&GD::gdSmallFont,
+		      $mm * $xratio / 2 - $FontWidth * 3 / 2 +
+		      $x, $ymax - $marginb + 4, (sprintf "%s", $m[$i]),
+		      $black)
+	if ($t + 2 * $w > $x_min) && ($x_max > 2 * $w + $t);
+      $t += $mm;
+      $t += $d if ($i == 0 || $i == 2 || $i == 4 ||
+		   $i == 6 || $i == 7 || $i == 9 || $i == 11); # 31 days months
+      if ($i == 1) {  # february ?
+	$t -= 2 * $d;
+	$t += $d unless (1970 + int ($t / $y)) % 4;
+      }
+      $i++;
+      $i = 0 if $i == 12; # Happy New Year !!
+    }
+  }
+
+  # Add the little red arrow
+  my $poly = new GD::Polygon;
+  $poly->addPt($xmax - $marginr - 2, $ymax - $marginb - 3);
+  $poly->addPt($xmax - $marginr + 4, $ymax - $marginb);
+  $poly->addPt($xmax - $marginr - 2, $ymax - $marginb + 3);
+  $image->filledPolygon($poly, $red);
+
+  # Title
+  $image->string (&GD::gdMediumBoldFont,
+		  $xmax / 2 - $FontWidth * length ($title) / 2, 4,
+		  $title, $black);
+
+  # Legend
+  my $y_in = $ymax - $size - $FontHeight + 5;
+  $image->string (&GD::gdSmallFont, $marginl, $y_in, $legend_in, $col_in);
+  $image->string (&GD::gdSmallFont, $xmax / 4, $y_in,
+		  (sprintf "Min: %5.1f $unit", $min_in * $factor), $black);
+  $image->string (&GD::gdSmallFont, $xmax / 2, $y_in,
+		(sprintf "Avg: %5.1f $unit", $s_in / $t_in * $factor), $black);
+  $image->string (&GD::gdSmallFont, 3 * $xmax / 4, $y_in,
+		  (sprintf "Max: %5.1f $unit", $max_in * $factor), $black);
+
+  my $y_out = $ymax - $size + 5;
+  $image->string (&GD::gdSmallFont, $marginl, $y_out, $legend_out, $col_out);
+  $image->string (&GD::gdSmallFont, $xmax / 4, $y_out,
+		  (sprintf "Min: %5.1f $unit", $min_out * $factor), $black);
+  $image->string (&GD::gdSmallFont, $xmax / 2, $y_out,
+	      (sprintf "Avg: %5.1f $unit", $s_out / $t_out * $factor), $black);
+  $image->string (&GD::gdSmallFont, 3 * $xmax / 4, $y_out,
+		  (sprintf "Max: %5.1f $unit", $max_out * $factor), $black);
+
+  open (IMG, "> $filename") || die "Error: Can't open \"$filename\": $!\n";
+  if ($GD_FORMAT eq 'png') {
+    print IMG $image->png;
+  }
+  else {
+    print IMG $image->gif;
+  }
+  close IMG;
+  return $ymax;
+}
+
+sub Write_all_results {
+  my $HTML_output = shift;
+  my $h = shift;
+  my $k;
+
+  my $title = $$h{'default'}{'title'} ?
+    $$h{'default'}{'title'} : "Daily Usenet report";
+  $title =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+  $title =~ s/\\\"/\"/go;
+  my $Title = $title;
+  $Title =~ s/<.*?>//go;
+  {
+    my $Title = $Title;
+    $Title =~ s/\&amp;/&/go;
+    $Title =~ s/\&lt;/</go;
+    $Title =~ s/\&gt;/>/go;
+    print "$Title from $first_date to $last_date\n\n";
+  }
+
+  if ($HTML) {
+    my $body = defined $output{'default'}{'html_body'} ?
+      $output{'default'}{'html_body'} : '';
+    $body =~ s/^\"\s*(.*?)\s*\"$/ $1/o;
+    $body =~ s/\\\"/\"/go;
+    open (HTML, "> $HTML_output") || die "Error: cant open $HTML_output\n";
+
+    print HTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" .
+      "<HTML>\n<HEAD>\n<TITLE>$Title: $first_date</TITLE>\n" .
+      "<!-- innreport $version -->\n</HEAD>\n<BODY $body>\n" .
+      "$HTML_header\n<CENTER><H1>$title</H1>\n" .
+      "<H3>$first_date -- $last_date</H3>\n</CENTER>\n<P><HR><P>\n";
+
+    # Index
+    print HTML "<UL>\n";
+    foreach $k (@{$$h{'_order_'}}) {
+      next if $k =~ m/^(default|index)$/;
+      my ($data) = $$h{$k}{'data'} =~ m/^\"\s*(.*?)\s*\"$/o;
+      $data =~ s/^\%/\%$CLASS\:\:/ unless $data eq '%prog_type';
+      my %data;
+      { local $^W = 0; no strict; %data = eval $data }
+      my ($string) = $$h{$k}{'title'} =~ m/^\"\s*(.*?)\s*\"$/o;
+      $string =~ s/\s*:$//o;
+      my $want = 1;
+
+      ($want) = $$h{$k}{'skip'} =~ m/^\"?\s*(.*?)\s*\"?$/o
+	if defined $$h{$k}{'skip'};
+      $want = $want eq 'true' ? 0 : 1;
+      print HTML "<LI><A HREF=\"#$k\">$string</A>\n" if %data && $want;
+    }
+    print HTML "</UL><P><HR><P>\n";
+  }
+  if (@unrecognize && $WANT_UNKNOWN) {
+    my $mm = $#unrecognize;
+    print HTML "<A NAME=\"unrecognize\">" if $HTML && $WANT_HTML_UNKNOWN;
+    print "Unknown entries from news log file:\n";
+    print HTML "<STRONG>Unknown entries from news log file:</STRONG></A><P>\n"
+      if $HTML && $WANT_HTML_UNKNOWN;
+    $mm = $MAX_UNRECOGNIZED - 1
+      if $MAX_UNRECOGNIZED > 0 && $mm > $MAX_UNRECOGNIZED - 1;
+    if ($mm < $unrecognize_max && $unrecognize_max > 0) {
+      printf HTML "First %d / $unrecognize_max lines (%3.1f%%)<BR>\n", $mm + 1,
+        ($mm + 1) / $unrecognize_max * 100 if $HTML && $WANT_HTML_UNKNOWN;
+      printf "First %d / $unrecognize_max lines (%3.1f%%)\n", $mm + 1,
+        ($mm + 1) / $unrecognize_max * 100;
+    }
+
+    my $l;
+    for $l (0 .. $mm) {
+      chomp $unrecognize[$l];     # sometimes, the last line need a CR
+      print "$unrecognize[$l]\n"; # so, we always add one
+      if ($HTML && $WANT_HTML_UNKNOWN) {
+	$unrecognize[$l] =~ s/&/\&amp;/g;
+	$unrecognize[$l] =~ s/</\&lt;/g;
+	$unrecognize[$l] =~ s/>/\&gt;/g;
+	print HTML "$unrecognize[$l]<BR>\n";
+      }
+    }
+    print "\n";
+    print HTML "<P><HR><P>\n" if $HTML && $WANT_HTML_UNKNOWN;
+  }
+
+  close HTML if $HTML;
+  foreach $k (@{$$h{'_order_'}}) {
+    next if $k =~ m/^(default|index)$/;
+    &Write_Results($HTML_output, $k, $h);
+  }
+  if ($HTML) {
+    open (HTML, ">> $HTML_output") || die "Error: cant open $HTML_output\n";
+    print HTML <<EOT;
+<A HREF="ftp://ftp.sofaraway.org/pub/innreport/">innreport</A>
+$version (c) 1996-1999 by Fabien Tassin
+&lt;<A HREF="mailto:fta\@sofaraway.org">fta\@sofaraway.org</A>&gt;.
+EOT
+    if (defined $$h{'default'}{'footer'}) {
+      my ($t) = $$h{'default'}{'footer'} =~ m/^\"\s*(.*?)\s*\"$/o;
+      $t =~ s/\\\"/\"/go;
+      print HTML "<BR>" . $t;
+    }
+    print HTML "\n$HTML_footer";
+    printf HTML "\n<!-- Running time: %s -->", second2time(time - $start_time);
+    print HTML "\n</BODY>\n</HTML>\n";
+    close HTML;
+  }
+}
+
+sub Write_Results {
+  my $HTML_output = shift;
+  my $report = shift;
+  my $data = shift;
+  my %output = %$data;
+  return 0 unless defined $output{$report}; # no data to write
+  return 0 if defined $output{$report}{'skip'} &&
+	       $output{$report}{'skip'} =~ m/^true$/io;
+  my ($TEXT, $HTML, $DOUBLE);
+
+  # Need a text report ?
+  $TEXT = defined $output{$report}{'text'} ? $output{$report}{'text'} :
+    (defined $output{'default'}{'text'} ? $output{'default'}{'text'} : '');
+  die "Error in config file. Field 'text' is mandatory.\n" unless $TEXT;
+  $TEXT = ($TEXT =~ m/^true$/io) ? 1 : 0;
+
+  # Need an html report ?
+  if ($HTML_output) {
+    $HTML = defined $output{$report}{'html'} ? $output{$report}{'html'} :
+      (defined $output{'default'}{'html'} ? $output{'default'}{'html'} : '');
+    die "Error in config file. Field 'html' is mandatory.\n" unless $HTML;
+    $HTML = ($HTML =~ m/^true$/io) ? 1 : 0;
+  }
+  # Double table ?
+  $DOUBLE = defined $output{$report}{'double'} ?
+    $output{$report}{'double'} : 0;
+  $DOUBLE = ($DOUBLE =~ m/^true$/io) ? 1 : 0;
+
+  # Want to truncate the report ?
+  my $TOP = defined $output{$report}{'top'} ? $output{$report}{'top'} : -1;
+  my $TOP_HTML = defined $output{$report}{'top_html'} ?
+                   $output{$report}{'top_html'} : $TOP;
+  my $TOP_TEXT = defined $output{$report}{'top_text'} ?
+                   $output{$report}{'top_text'} : $TOP;
+
+  my (%h, %d, $h);
+  {
+    my $t = $output{$report}{'data'} ||
+      die "Error in section $report. Need a 'data' field.\n";
+    $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+    $t =~ s/^\%/\%$CLASS\:\:/ unless $t eq '%prog_type';
+    %d = eval $t;
+    return unless %d; # nothing to report. exit.
+    return unless keys (%d); # nothing to report. exit.
+  }
+  {
+    my $t = defined $output{$report}{'sort'} ? $output{$report}{'sort'} :
+      "\$a cmp \$b";
+    $t =~ s/\n/ /smog;
+    $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+    $t =~ s/([\$\%\@])/$1${CLASS}\:\:/go;
+    $t =~ s/([\$\%\@])${CLASS}\:\:(prog_(?:size|type)|key|num)/$1$2/go;
+    $t =~ s/\{\$${CLASS}\:\:(a|b)\}/\{\$$1\}/go;
+    $t =~ s/\$${CLASS}\:\:(a|b)/\$$1/go;
+    $h = $t;
+  }
+
+  if ($HTML) {
+    open (HTML, ">> $HTML_output") || die "Error: cant open $HTML_output\n";
+  }
+  print "\n" if $TEXT;
+  my ($key, $key1, $key2);
+  if (defined $output{$report}{'title'}) {
+    my $t = $output{$report}{'title'};
+    $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+    if ($HTML) {
+      print HTML "<A NAME=\"$report\">";
+      my $html = $t;
+      $html =~ s/(:?)$/ [Top $TOP_HTML]$1/o if $TOP_HTML > 0;
+      $html =~ s|^(.*)$|<STRONG>$1</STRONG>|;
+      print HTML "$html</A>\n<P>\n<CENTER>\n<TABLE BORDER=\"1\">\n";
+    }
+    $t =~ s/(:?)$/ [Top $TOP_TEXT]$1/o if $TOP_TEXT > 0;
+    print "$t\n" if $TEXT;
+  }
+  my $numbering = 0;
+  $numbering = 1 if defined $output{$report}{'numbering'} &&
+                    $output{$report}{'numbering'} =~ m/^true$/o;
+  my $i;
+  my $s = '';
+  my $html = '';
+  my $first = 0;
+
+  foreach $i (@{$output{$report}{'column'}}) {
+    my ($v1, $v2);
+
+    my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+    $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+    my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+    $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+    $v1 = defined ($$i{'format_name'}) ? $$i{'format_name'} :
+      (defined ($$i{'format'}) ? $$i{'format'} : "%s");
+    $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+    $v2 = $$i{'name'};
+    $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+    $s .= sprintf $v1 . " ", $v2 if $wtext && !($DOUBLE && $first == 1);
+    if ($HTML && $whtml) {
+      my $v1 = $v1;
+      $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?(\w)/\%$1/g;
+      my $temp = $first ? "CENTER" : "LEFT";
+      $temp .= "\" COLSPAN=\"2" if $numbering && !$first;
+      $html .= sprintf "<TH ALIGN=\"$temp\">$v1</TH>", $v2;
+    }
+    $first++;
+  }
+  $s =~ s/\s*$//;
+  print "$s\n" if $TEXT;
+  $s = '';
+  if ($HTML) {
+    print HTML "<TR>$html</TR>\n<TR><TD></TD></TR>\n";
+    $html = '';
+  }
+  my $num = 0;
+  my $done;
+  if ($DOUBLE) {
+    my $num_d = 0;
+    foreach $key1 (sort keys (%d)) {
+      $done = 0;
+      $num = 0;
+      $num_d++;
+      $s = '';
+      $html = '';
+      my @res;
+      foreach $key2 (sort {$d{$key1}{$b} <=> $d{$key1}{$a}}
+		     keys (%{$d{$key1}})) {
+	my $first = 0;
+	$num++;
+	foreach $i (@{$output{$report}{'column'}}) {
+	  my ($v1, $v2, $p);
+
+	  my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+	  $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+	  my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+	  $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+	  # is it the primary key ?
+	  $p = 0;
+	  $p = 1 if defined $$i{'primary'} && $$i{'primary'} =~ m/true/;
+
+	  # format
+	  $v1 = defined ($$i{'format'}) ? $$i{'format'} : "%s";
+	  $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+
+	  # value
+	  $v2 = $$i{'value'};
+	  $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  my $r ='';
+	  if ($v2) {
+	    $r = &EvalExpr ($v2, $key2, $num, $key1);
+	    die "Error in section $report column $$i{'name'}. " .
+	      "Invalid 'value' value.\n" unless defined $r;
+	  }
+	  $res[$first] += $r if $v1 =~ m/\%-?(?:\d+(?:\.\d+)?)?d/o;
+	  if ($p) {
+	    $s .= sprintf $v1. "\n", $r unless $done || !$wtext;
+	    if ($HTML && $whtml) {
+	      if ($done) {
+		$html .= "<TD></TD>";
+	      }
+	      else {
+		$v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+		$html .= $numbering ? "<TH ALIGN=\"CENTER\">$num_d</TH>" : '';
+		#  unless $first;
+		$html .= sprintf "<TD ALIGN=\"LEFT\">$v1</TD></TR>\n", $r;
+		$html .= "<TR><TD></TD>";
+	      }
+	    }
+	  }
+	  else {
+	    if ($wtext) {
+	      $s .= "  " if $first == 1;
+	      $s .= sprintf $v1 . " ", $r;
+	    }
+	    if ($HTML && $whtml) {
+	      $html .= $numbering ? "<TD></TD>" : '' if $first == 1;
+	      $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+	      my $temp = $first > 1 ? "RIGHT" : "LEFT";
+	      $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+	    }
+	  }
+	  $done = 1 if $p;
+	  $first++;
+	}
+	$s =~ s/\s*$//;
+	$s =~ s/\\n/\n/g;
+	print "$s\n" if $TEXT && ($num <= $TOP_TEXT || $TOP_TEXT == -1);
+	if ($HTML && ($num <= $TOP_HTML || $TOP_HTML == -1)) {
+	  $html =~ s/\\n//g;
+	  print HTML "<TR>$html</TR>\n";
+	}
+	$s = '';
+	$html = '';
+      }
+      $first = 0;
+      $s = '';
+      $html = '';
+      if ($TOP_TEXT != -1 && $TOP_HTML != -1) {
+	foreach $i (@{$output{$report}{'column'}}) {
+	  if (defined $$i{'primary'} && $$i{'primary'} =~ m/true/o) {
+	    $first++;
+	    $s .= '  ';
+	    $html .= "<TD></TD>" if $HTML;
+	    $html .= "<TD></TD>" if $HTML && $numbering;
+	    next;
+	  }
+	  my ($v1, $v2);
+	  $v1 = defined ($$i{'format_total'}) ? $$i{'format_total'} :
+	    (defined ($$i{'format'}) ? $$i{'format'} : "%s");
+	  $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  my $r = $first == 1 ? $num : $res[$first];
+	  $s .= sprintf $v1 . " ", $r;
+	  if ($HTML) {
+	    my $temp = $first > 1 ? "RIGHT" : "LEFT";
+	    $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+	    $v1 =~ s|(.*)|<STRONG>$1</STRONG>|o unless $first > 1;
+	    $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+	  }
+	  $first++;
+	}
+	$s =~ s/\s*$//;
+	$s =~ s/\\n//g;
+	print "$s\n" if $TEXT;
+	print HTML "<TR>$html</TR>\n" if $HTML;
+      }
+    }
+    print "\n" if $TEXT;
+    print HTML "<TR><TD></TD></TR>\n" if $HTML;
+    $first = 0;
+    $num = $num_d;
+    $s = '';
+    $html = '';
+    foreach $i (@{$output{$report}{'column'}}) {
+      my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+      $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+      my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+      $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+      my ($v1, $v2);
+      $v1 = defined $$i{'format_total'} ? $$i{'format_total'} :
+	(defined $$i{'format'} ? $$i{'format'} : "%s");
+      $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+      $v2 = $$i{'total'} ||
+	die "Error in section $report column $$i{'name'}. " .
+	  "Need a 'total' field.\n";
+      $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+      my $r = '';
+      if ($v2) {
+	$r = &EvalExpr ($v2, $key2, $num, 1);
+	die "Error in section $report column $$i{'name'}. " .
+	  "Invalid 'total' value.\n" unless defined $r;
+      }
+      $s .= sprintf $v1 . " ", $r if $wtext && $first != 1;
+      if ($HTML && $whtml) {
+	my $temp = $first ? "RIGHT" : "LEFT";
+	$temp .= "\" COLSPAN=\"2" if $numbering && !$first;
+	$v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+	$v1 =~ s|(.*)|<STRONG>$1</STRONG>|o unless $first;
+	$html .= $first == 1 ? "<TD></TD>" :
+	         sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+      }
+      $first++;
+    }
+    $s =~ s/\s*$//;
+    $s =~ s/\\n//g;
+    print "$s\n" if $TEXT;
+    print HTML "<TR>$html</TR>\n</TABLE>\n</CENTER>\n<P>\n<HR>\n" if $HTML;
+  }
+  else {
+    # foreach $key (sort { local $^W = 0; no strict; eval $h } (keys (%d)))
+    foreach $key ((eval "sort {local \$^W = 0; no strict; $h} (keys (%d))")) {
+      next unless defined $key;
+      next unless defined $d{$key}; # to avoid problems after some undef()
+      $num++;
+      next unless $num <= $TOP_HTML || $TOP_HTML == -1 ||
+	          $num <= $TOP_TEXT || $TOP_TEXT == -1;
+      my $first = 0;
+      foreach $i (@{$output{$report}{'column'}}) {
+	my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+	$wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+	my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+	$whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+	my ($v1, $v2);
+	$v1 = defined ($$i{'format'}) ? $$i{'format'} : "%s";
+	$v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	$v2 = $$i{'value'};
+	$v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	my $r ='';
+	if ($v2) {
+	  $r = &EvalExpr ($v2, $key, $num);
+	  die "Error in section $report column $$i{'name'}. " .
+	    "Invalid 'value' value.\n" unless defined $r;
+	}
+	$s .= sprintf $v1 . " ", $r
+	  if $wtext && (($num <= $TOP_TEXT) || ($TOP_TEXT == -1));
+	if ($HTML && $whtml && ($num <= $TOP_HTML || $TOP_HTML == -1)) {
+	  $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+	  $html .= "<TH ALIGN=\"CENTER\">$num</TH>" if $numbering && !$first;
+	  my $temp = $first ? "RIGHT" : "LEFT";
+	  $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+	}
+	$first++;
+      }
+      $s =~ s/\s*$//;
+      print "$s\n" if $TEXT && ($num <= $TOP_TEXT || $TOP_TEXT == -1);
+      $s = '';
+      if ($HTML && ($num <= $TOP_HTML || $TOP_HTML == -1)) {
+	print HTML "<TR>$html</TR>\n";
+	$html = '';
+      }
+    }
+    print "\n" if $TEXT;
+    print HTML "<TR><TD></TD></TR>\n" if $HTML;
+    $first = 0;
+    foreach $i (@{$output{$report}{'column'}}) {
+      my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+      $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+      my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+      $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+      my ($v1, $v2);
+      $v1 = defined ($$i{'format_total'}) ? $$i{'format_total'} :
+	(defined ($$i{'format'}) ? $$i{'format'} : "%s");
+      $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+      $v2 = $$i{'total'} ||
+	die "Error in section $report column $$i{'name'}. " .
+	  "Need a 'total' field.\n";
+      $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+      my $r = '';
+      if ($v2) {
+	$r = &EvalExpr ($v2, $key, $num);
+	die "Error in section $report column $$i{'name'}. " .
+	  "Invalid 'total' value.\n" unless defined $r;
+      }
+      $s .= sprintf $v1 . " ", $r if $wtext;
+      if ($HTML && $whtml) {
+	$v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+	my $temp = $first ? "RIGHT" : "LEFT";
+	$temp .= "\" COLSPAN=\"2" if $numbering && !$first;
+	$v1 =~ s|(.*)|<STRONG>$1</STRONG>|o unless $first;
+	$html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+      }
+      $first++;
+    }
+    $s =~ s/\s*$//;
+    print "$s\n" if $TEXT;
+    if ($HTML) {
+      print HTML "<TR>$html</TR>\n";
+      print HTML "</TABLE>\n</CENTER><P>\n";
+
+      my $i = 0;
+      while ($GRAPH && defined ${${$output{$report}{'graph'}}[$i]}{'type'}) {
+	my $type = ${${$output{$report}{'graph'}}[$i]}{'type'};
+        my ($title) = ${${$output{$report}{'graph'}}[$i]}{'title'} =~
+	               m/^\"\s*(.*?)\s*\"$/o;
+        if ($type eq 'histo3d') {
+	  my (@values, @colors, @labels);
+	  my $num = 0;
+	  my $j;
+	  foreach $j (@{${${$output{$report}{'graph'}}[$i]}{'data'}}) {
+	    $num++;
+	    my ($h) = $$j{'value'} =~ m/^\"\s*(.*?)\s*\"$/o;
+	    my %hh;
+	    $h =~ s/^\%/\%$CLASS\:\:/ unless $h eq '%prog_type';
+	    { local $^W = 0; no strict; %hh = eval $h }
+	    push @values, \%hh;
+	    my ($t) = $$j{'name'} =~ m/^\"\s*(.*?)\s*\"$/o;
+	    push @labels, $t;
+	    $t = $$j{'color'} ||
+	      die "Error in section $report section 'graph'. " .
+		"No color specified for 'value' $$j{'value'}.\n";
+	    $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+	    $t =~ m/^[\da-fA-F]{6}$/o ||
+	      die "Error in section $report section 'graph'. " .
+		"Bad color for 'value' $$j{'value'}.\n";
+	    my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+	    push @colors, \@c;
+	  }
+	  $suffix = '' unless defined $suffix;
+	  my $s = ($i ? $i : '') . $suffix;
+	  print HTML "<CENTER><IMG ALT=\"$title\" ";
+	  close HTML;
+	  my $y = &Graph3d ("$IMG_dir/$report$s.$GD_FORMAT",
+		    $title, $xmax, $num, @values, \@colors, \@labels);
+	  open (HTML, ">> $HTML_output") ||
+	    die "Error: cant open $HTML_output\n";
+	  print HTML "WIDTH=\"$xmax\" HEIGHT=\"$y\" ";
+	  print HTML "SRC=\"$IMG_pth$report$s.$GD_FORMAT\"></CENTER>\n";
+	}
+        elsif ($type eq 'histo') {
+	  my (%values, %labels);
+	  my $factor =
+	    ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'factor'}
+             || die "Error in section $report section 'graph'. " .
+	       "No factor specified for 'value' " .
+	       ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'name'} .
+	       ".\n";
+	  $factor =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  my $labelx =
+	    ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[0]}{'name'}
+	     || die "Error in section $report section 'graph'. " .
+	       "No name specified for value.\n";
+	  $labelx =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  my $labely =
+	    ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'name'}
+	     || die "Error in section $report section 'graph'. " .
+	       "No name specified for value.\n";
+	  $labely =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  my $t = ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[0]}{'value'}
+	     || die "Error in section $report section 'graph'. " .
+	       "No 'value' specified for " .
+	       ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[0]}{'name'} .
+	       ".\n";
+	  $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  $t =~ s/^\%/\%$CLASS\:\:/ unless $t eq '%prog_type';
+	  { local $^W = 0; no strict; %labels = eval $t }
+
+	  $t = ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'value'} ||
+	     die "Error in section $report section 'graph'. " .
+	       "No 'value' specified for " .
+	       ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'name'} .
+	       ".\n";
+	  $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+	  $t =~ s/^\%/\%$CLASS\:\:/ unless $t eq '%prog_type';
+	  { local $^W = 0; no strict; %values = eval $t }
+	  my $s = ($i ? $i : '') . $suffix;
+	  {
+	    my $r;
+	    close HTML;
+	    $r = &Histo ("$IMG_dir/$report$s.$GD_FORMAT", $title, $xmax,
+			 $factor, $labelx, $labely, \%values, \%labels);
+	    open (HTML, ">> $HTML_output") ||
+	      die "Error: cant open $HTML_output\n";
+	    print HTML "<CENTER><IMG ALT=\"$title\" WIDTH=\"$xmax\" " .
+	      "SRC=\"$IMG_pth$report$s.$GD_FORMAT\"></CENTER>\n" if $r;
+	  }
+	}
+        elsif ($type eq 'piechart') {
+	  print "Sorry, graph type 'piechart' not supported yet..\n";
+	}
+        else {
+	  die "Error in section $report section 'graph'. " .
+	    "Invalid 'type' value.\n"
+	}
+	$i++;
+	print HTML "<P>\n";
+      }
+      print HTML "\n<HR>\n";
+    }
+  }
+  close HTML if $HTML;
+}
+
+sub EvalExpr {
+  my $v = shift;
+  my ($key, $num, $key1) = @_;
+  my $key2;
+
+  $v =~ s/\n/ /smog;
+  $v =~ s/^\"(.*?)\"$/$1/o;
+  if ($key1) {
+    $key2 = $key;
+    $v =~ s/([^a-zA-Z_\-]?)total\s*\(\s*%/$1&ComputeTotalDouble\(\\%/og;
+  }
+  else {
+    $v =~ s/([^a-zA-Z_\-]?)total\s*\(\s*%/$1&ComputeTotal\(\\%/og;
+    # $v =~ s/([^a-zA-Z_\-]?)total\s*\(\s*%([^\)]*)\)/$1&ComputeTotal\("$2"\)/og;
+  }
+  $v =~ s/([^a-zA-Z_\-]?)bytes\s*\(\s*/$1&NiceByte\(/og;
+  $v =~ s/([^a-zA-Z_\-]?)time\s*\(\s*/$1&second2time\(/og;
+  $v =~ s/([^a-zA-Z_\-]?)time_ms\s*\(\s*/$1&ms2time\(/og;
+  # $v =~ s/([\$\%\@])/$1${CLASS}\:\:/og;
+  $v =~ s/([\$\%\@])([^{\s\d])/$1${CLASS}\:\:$2/og;
+  $v =~ s/([\$\%\@])${CLASS}\:\:(prog_(?:size|type)|key|sec_glob|num)/$1$2/og;
+  my $r;
+  # eval { local $^W = 0; no strict; ($r) = eval $v; };
+  eval " local \$^W = 0; no strict; (\$r) = $v; ";
+  $r = 0 unless defined $r;
+  $r;
+}
+
+sub NiceByte {
+  my $size = shift;
+  my $t;
+
+  $size = 0 unless defined $size;
+  $t = $size / 1024 / 1024 / 1024 > 1 ?
+    sprintf "%.1f GB", $size / 1024 / 1024 / 1024 :
+      ($size / 1024 / 1024 > 1 ? sprintf "%.1f MB", $size / 1024 / 1024 :
+	sprintf "%.1f KB", $size / 1024);
+  return $t;
+}
+
+sub kb2i {
+  my $s = shift;
+  my ($i, $u) = $s =~ m/^(\S+) (\S+)$/;
+  $i *= 1024 * 8 if $u =~ m/MB/o;
+  $i *= 1024 * 1024 * 8 if $u =~ m/GB/o;
+  return $i;
+}
+
+sub Decode_Config_File {
+  my $file = shift;
+  my ($line, $section);
+  my $linenum = 0;
+  my $info;
+  my @list;
+  open (FILE, "$file") || die "Can\'t open config file \"$file\". Abort.\n";
+  while (defined ($line = <FILE>)) {
+    $linenum++;
+    last if eof (FILE);
+    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+    die "Error in $file line $linenum: must be 'section' instead of '$info'\n"
+      unless ($info eq 'section');
+    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+    die "Error in $file line $linenum: invalid section name '$info'\n"
+      unless $info =~ /^\w+$/;
+    print "section $info {\n" if $DEBUG;
+    $section = $info;
+    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+    die "Error in $file line $linenum: must be a '{' instead of '$info'\n"
+      unless ($info eq '{');
+    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+    push @list, $section;
+    while ($info ne '}') { # it is a block
+      last if eof (FILE);
+      my $keyword = $info;
+      ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+      my $value = $info;
+      if ($info eq '{') { # it is a sub-block
+	my @a;
+	$output{$section}{$keyword} = \@a unless $output{$section}{$keyword};
+	my %hash;
+	print "\t$keyword {\n" if $DEBUG;
+	($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	my @sublist; # to store the "data" blocks
+
+	while ($info ne '}') {
+	  last if eof (FILE);
+	  my $subkeyword = $info;
+	  ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	  my $subvalue = $info;
+	  if ($info eq '{') {
+	    # it is a sub-sub-block
+	    my %subhash;
+	    print "\t\t$subkeyword {\n" if $DEBUG;
+	    my @b;
+	    $hash{$subkeyword} = \@b unless ${hash}{$subkeyword};
+	    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	    while ($info ne '}') {
+	      last if eof (FILE);
+	      my $subsubkeyword = $info;
+	      ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	      my $subsubvalue = $info;
+	      if ($info eq '{') {
+		die "Error in $file line $linenum: too many blocks.\n";
+	      }
+	      else {
+		($info, $linenum, $line) =
+		  &read_conf ($linenum, $line, \*FILE);
+		die "Error in $file line $linenum: must be a ';' instead " .
+		  "of '$info'\n" unless ($info eq ';');
+		print "\t\t\t$subsubkeyword\t$subsubvalue;\n" if $DEBUG;
+		$subhash{$subsubkeyword} = $subsubvalue;
+		($info, $linenum, $line) =
+		  &read_conf ($linenum, $line, \*FILE);
+	      }
+	    }
+	    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	    die "Error in $file line $linenum: must be a ';' instead of " .
+	      "'$info'\n" unless $info eq ';';
+	    push @{$hash{$subkeyword}} , \%subhash;
+            ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	    print "\t\t};\n" if $DEBUG;
+	  }
+	  else {
+	    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	    die "Error in $file line $linenum: must be a ';' instead " .
+	      "of '$info'\n" unless $info eq ';';
+	    print "\t\t$subkeyword\t$subvalue;\n" if $DEBUG;
+	    $hash{$subkeyword} = $subvalue;
+	    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	  }
+	}
+	($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+        die "Error in $file line $linenum: must be a ';' instead of '$info'\n"
+	  unless $info eq ';';
+	push @{$output{$section}{$keyword}}, \%hash;
+	($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+	print "\t};\n" if $DEBUG;
+      }
+      else {
+	($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+        die "Error in $file line $linenum: must be a ';' instead of '$info'\n"
+	  unless $info eq ';';
+	print "\t$keyword\t$value;\n" if $DEBUG;
+	$output{$section}{$keyword} = $value;
+	($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+      }
+    }
+    die "Error in $file line $linenum: must be a '}' instead of '$info'\n"
+      unless $info eq '}';
+    ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+    die "Error in $file line $linenum: must be a ';' instead of '$info'\n"
+      unless $info eq ';';
+    print "};\n\n" if $DEBUG;
+  }
+  close FILE;
+  $output{'_order_'} = \@list;
+}
+
+sub read_conf {
+  my ($linenum, $line, $file) = @_;
+  *FILE = *$file;
+
+  $line =~ s,^\s+,,o;            # remove useless blanks
+  $line =~ s,^(\#|//).*$,,o;     # remove comments (at the beginning)
+  while (($line =~ m/^$/o || $line =~ m/^\"[^\"]*$/o) && !(eof (FILE))) {
+    $line .= <FILE>;             # read one line
+    $linenum++;
+    $line =~ s,^\s*,,om;         # remove useless blanks
+    $line =~ s,^(\#|//).*$,,om;  # remove comments (at the beginning)
+  }
+  $line =~ s/^(                  # at the beginning
+	       [{};]             # match '{', '}', or ';'
+	      |                  # OR
+	       \"                # a double quoted string
+                (?:\\.|[^\"\\])*
+               \"
+	      |                  # OR
+               [^{};\"\s]+       # a word
+             )\s*//mox;
+  my $info = $1;
+  if (defined $info && $info) {
+    chomp $info;
+  }
+  else {
+    warn "Syntax error in conf file line $linenum.\n";
+  }
+  return ($info, $linenum, $line);
+}
+
+sub GetValue {
+  my $v = shift;
+  my ($r) = $v =~ m/^(?:\"\s*)?(.*?)(?:\s*\")?$/so;
+  return $r;
+}
+
+sub Usage {
+  my ($base) = $0 =~ /([^\/]+)$/;
+  print "Usage: $base -f innreport.conf [-[no]options]\n";
+  print "  where options are:\n";
+  print "    -h (or -help)       this help page\n";
+  print "    -v                  display the version number of INNreport\n";
+  print "    -config             print INNreport configuration information\n";
+  print "    -html               HTML output";
+  print " [default]" if ($HTML);
+  print "\n";
+  print "    -g                  want graphs";
+  print " [default]" if ($GRAPH);
+  print "\n";
+  print "    -graph              an alias for option -g\n";
+  print "    -d directory        directory for Web pages";
+  print "\n                        [default=$HTML_dir]"
+    if (defined ($HTML_dir));
+  print "\n";
+  print "    -dir directory      an alias for option -d\n";
+  print "    -p directory        pictures path (file space)";
+  print "\n                        [default=$IMG_dir]"
+    if (defined ($IMG_dir));
+  print "\n";
+  print "    -path directory     an alias for option -p\n";
+  print "    -w directory        pictures path (web space)";
+  print " [default=$IMG_pth]" if (defined ($IMG_pth));
+  print "\n";
+  print "    -webpath directory  an alias for option -w\n";
+  print "\n";
+  print "    -i file             Name of index file";
+  print " [default=$index]" if (defined ($index));
+  print "\n";
+  print "    -index file         an alias for option -i\n";
+  print "    -a                  want to archive HTML results";
+  print " [default]" if ($ARCHIVE);
+  print "\n";
+  print "    -archive            an alias for option -a\n";
+  print "    -c number           how many report files to keep (0 = all)\n";
+  print "                        [default=$CYCLE]"
+    if (defined ($CYCLE));
+  print "\n";
+  print "    -cycle number       an alias for option -c\n";
+  print "    -s char             separator for filename";
+  print " [default=\"$SEPARATOR\"]\n";
+  print "    -separator char     an alias for option -s\n";
+  print "    -unknown            \"Unknown entries from news log file\"\n";
+  print "                        report";
+  print " [default]" if ($WANT_UNKNOWN);
+  print "\n";
+  print "    -html-unknown       Same as above, but in generated HTML output.";
+  print " [default]" if ($WANT_UNKNOWN);
+  print "\n";
+  print "    -maxunrec           Max number of unrecognized lines to display\n";
+  print "                        [default=$MAX_UNRECOGNIZED]"
+    if (defined ($MAX_UNRECOGNIZED));
+  print "\n";
+  print "    -notdaily           Never perform daily actions";
+  print " [default]" if $NOT_DAILY;
+  print "\n";
+  print "    -casesensitive      Case sensitive";
+  print " [default]" if ($CASE_SENSITIVE);
+  print "\n\n";
+  print "Use no in front of boolean options to unset them.\n";
+  print "For example, \"-html\" is set by default. Use \"-nohtml\" to remove this\n";
+  print "feature.\n";
+  exit 0;
+}
+
+sub Version {
+  print "\nThis is INNreport version $version\n\n";
+  print "Copyright 1996-1999, Fabien Tassin <fta\@sofaraway.org>\n";
+  exit 0;
+}
+
+sub Summary {
+  use Config;
+
+  # Convert empty arguments into null string ("")
+  my $i = 0;
+  foreach (@old_argv) {
+    $old_argv[$i] = '""' if $_ eq '';
+    $i++;
+  }
+
+  # Display the summary
+  print "\nSummary of my INNreport (version $version) configuration:\n";
+  print "  General options:\n";
+  print "    command line='@old_argv' (please, check this value)\n";
+  print "    html=" . ($HTML?"yes":"no") . ", graph=" .
+                      ($GRAPH?"yes":"no") . ", haveGD=" .
+                      ($::HAVE_GD?"yes":"no") . "\n";
+  print "    archive=" . ($ARCHIVE?"yes":"no") .
+                      ", cycle=$CYCLE, separator=\"" . $SEPARATOR . "\"\n";
+  print "    case_sensitive=" .
+                      ($CASE_SENSITIVE?"yes":"no") . ", want_unknown=" .
+		      ($WANT_UNKNOWN?"yes":"no") .
+                      ", max_unrecog=$MAX_UNRECOGNIZED\n";
+  print "  Paths:\n";
+  print "    html_dir=$HTML_dir\n";
+  print "    img_dir=$IMG_dir\n";
+  print "    img_pth=$IMG_pth\n";
+  print "    index=$index\n";
+  print "  Platform:\n";
+  print "    perl version $::Config{baserev} "
+            . "patchlevel $::Config{patchlevel} "
+            . "subversion $::Config{subversion}\n";
+  print "    libperl=$::Config{libperl}, useshrplib=$::Config{useshrplib}, "
+       . "bincompat3=$::Config{bincompat3}\n";
+  print "    osname=$::Config{osname}, osvers=$::Config{osvers}, "
+        . "archname=$::Config{archname}\n";
+  print "    uname=$::Config{myuname}\n\n";
+
+  exit 0;
+}
+
+######################### End of File ##########################
--- /dev/null
+++ b/extra/innreport_inn.pm
@@ -0,0 +1,2101 @@
+##########################################################
+# INN module for innreport (3.*).
+#
+# Sample file tested with INN 2.3, 2.2, 1.7.2 and 1.5.1
+#
+# (c) 1997-1999 by Fabien Tassin <fta@sofaraway.org>
+# version 3.0.2
+##########################################################
+
+# TODO: add the map file.
+
+package innreport_inn;
+
+my $MIN = 1E10;
+my $MAX = -1;
+
+my %ctlinnd = ('a', 'addhist',     'D', 'allow',
+	       'b', 'begin',       'c', 'cancel',
+	       'u', 'changegroup', 'd', 'checkfile',
+	       'e', 'drop',        'f', 'flush',
+	       'g', 'flushlogs',   'h', 'go',
+	       'i', 'hangup',      's', 'mode',
+	       'j', 'name',        'k', 'newgroup',
+	       'l', 'param',       'm', 'pause',
+	       'v', 'readers',     't', 'refile',
+	       'C', 'reject',      'o', 'reload',
+	       'n', 'renumber',    'z', 'reserve',
+	       'p', 'rmgroup',     'A', 'send',
+	       'q', 'shutdown',    'B', 'signal',
+	       'r', 'throttle',    'w', 'trace',
+	       'x', 'xabort',      'y', 'xexec',
+	       'E', 'logmode',     'F', 'feedinfo',
+	       'T', 'filter',      'P', 'perl',);
+
+my %timer_names = (idle     => 'idle',
+                   hishave  => 'history lookup',
+                   hisgrep  => 'history grep',
+                   hiswrite => 'history write',
+                   hissync  => 'history sync',
+                   artlink  => 'article link',
+                   artwrite => 'article write',
+                   artcncl  => 'article cancel',
+				   artctrl  => 'article control',
+                   sitesend => 'site send',
+                   perl     => 'perl filter',
+);
+
+my %innfeed_timer_names = (
+                   'idle'    => 'idle',
+		   'blstats' => 'backlog stats',
+		   'stsfile' => 'status file',
+		   'newart'  => 'article new',
+		   'prepart' => 'article prepare',
+		   'readart' => 'article read',
+		   'read'    => 'data read',
+		   'write'   => 'data write',
+		   'cb'      => 'callbacks',
+);
+
+my %nnrpd_timer_names = (
+                   'idle'    => 'idle',
+                   'newnews' => 'newnews',
+);
+
+# init innd timer
+foreach (values %timer_names) {
+  $innd_time_min{$_} = $MIN;
+  $innd_time_max{$_} = $MAX;
+  $innd_time_time{$_} = 0;   # to avoid a warning... Perl < 5.004
+  $innd_time_num{$_} = 0;    # ...
+}
+$innd_time_times = 0;        # ...
+
+# init innfeed timer
+foreach (values %innfeed_timer_names) {
+  $innfeed_time_min{$_} = $MIN;
+  $innfeed_time_max{$_} = $MAX;
+  $innfeed_time_time{$_} = 0;   # to avoid a warning... Perl < 5.004
+  $innfeed_time_num{$_} = 0;    # ...
+}
+$innfeed_time_times = 0;        # ...
+
+# init nnrpd timer
+foreach (values %nnrpd_timer_names) {
+  $nnrpd_time_min{$_} = $MIN;
+  $nnrpd_time_max{$_} = $MAX;
+  $nnrpd_time_time{$_} = 0;   # to avoid a warning... Perl < 5.004
+  $nnrpd_time_num{$_} = 0;    # ...
+}
+$nnrpd_time_times = 0;        # ...
+
+# collect: Used to collect the data.
+sub collect {
+  my ($day, $hour, $prog, $res, $left, $CASE_SENSITIVE) = @_;
+
+  return 1 if $left =~ /Reading config from (\S+)$/o;
+
+  ########
+  ## inn (from the "news" log file - not from "news.notice")
+  ##
+  if ($prog eq "inn") {
+    # accepted article
+    if ($res =~ m/[\+j]/o) {
+      $hour =~ s/:.*$//o;
+      $inn_flow{"$day $hour"}++;
+      $inn_flow_total++;
+
+      # Memorize the size. This can only be done with INN >= 1.5xx and
+      # DO_LOG_SIZE = DO.
+
+      # server <msg-id> size [feeds]
+      # or
+      # server <msg-id> (filename) size [feeds]
+
+      my ($s) = $left =~ /^\S+ \S+ (?:\(\S+\) )?(\d+)(?: |$)/o;
+      if ($s) {
+	$inn_flow_size{"$day $hour"} += $s;
+	$inn_flow_size_total += $s;
+      }
+      return 1;
+    }
+
+    # 437 Duplicate article
+    if ($left =~ /(\S+) <[^>]+> 437 Duplicate(?: article)?$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_duplicate{$server}++;
+      return 1;
+    }
+    # 437 Unapproved for
+    if ($left =~ /(\S+) <[^>]+> 437 Unapproved for \"([^\"]+)\"$/o) {
+      my ($server, $group) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_unapproved{$server}++;
+      $inn_unapproved_g{$group}++;
+      return 1;
+    }
+    # 437 Too old -- ...
+    if ($left =~ /(\S+) <[^>]+> 437 Too old -- /o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_tooold{$server}++;
+      return 1;
+    }
+    # 437 Unwanted site ... in path
+    if ($left =~ /(\S+) <[^>]+> 437 Unwanted site (\S+) in path$/o) {
+      my ($server, $site) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_uw_site{$server}++;
+      $inn_site_path{$site}++;
+      return 1;
+    }
+    # 437 Unwanted newsgroup "..."
+    if ($left =~ /(\S+) <[^>]+> 437 Unwanted newsgroup \"(\S+)\"$/o) {
+      my ($server, $group) = ($1, $2);
+      ($group) = split(/,/, $group);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_uw_ng_s{$server}++;
+      $inn_uw_ng{$group}++;
+      return 1;
+    }
+    # 437 Unwanted distribution "..."
+    if ($left =~ /(\S+) <[^>]+> 437 Unwanted distribution \"(\S+)\"$/o) {
+      my ($server, $dist) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_uw_dist_s{$server}++;
+      $inn_uw_dist{$dist}++;
+      return 1;
+    }
+    # 437 Linecount x != y +- z
+    if ($left =~ /(\S+) <[^>]+> 437 Linecount/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $inn_linecount{$server}++;
+      return 1;
+    }
+    # 437 No colon-space in "xxxx" header
+    if ($left =~ /(\S+) <[^>]+> 437 No colon-space in \"[^\"]+\" header/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $innd_others{$server}++;
+      $innd_no_colon_space{$server}++;
+      return 1;
+    }
+    # 437 Article posted in the future -- "xxxxx"
+    if ($left =~ /(\S+) <[^>]+> 437 Article posted in the future -- \"[^\"]+\"/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_posted_future{$server}++;
+      $innd_others{$server}++;
+      $inn_badart{$server}++;
+      return 1;
+    }
+    # 437 article includes "....."
+    if ($left =~ /(\S+) <[^>]+> 437 article includes/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_strange_strings{$server}++;
+      $innd_others{$server}++;
+      $inn_badart{$server}++;
+      return 1;
+    }
+    # Cancelling <...>
+    if ($left =~ /(\S+) <[^>]+> Cancelling/o) {
+      return 1;
+    }
+    # all others are just counted as "Other"
+    if ($left =~ /(\S+) /o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $inn_badart{$server}++;
+      $innd_others{$server}++;
+      return 1;
+    }
+  }
+
+  ########
+  ## innd
+  if ($prog eq "innd") {
+    ## Note for innd logs:
+    ## there's a lot of entries detected but still not used
+    ## (because of a lack of interest).
+
+    # think it's a dotquad
+    return 1 if $left =~ /^think it\'s a dotquad$/o;
+    if ($left =~ /^SERVER /o) {
+      # SERVER perl filtering enabled
+      return 1 if $left =~ /^SERVER perl filtering enabled$/o;
+      # SERVER perl filtering disabled
+      return 1 if $left =~ /^SERVER perl filtering disabled$/o;
+      # SERVER cancelled +id
+      return 1 if $left =~ /^SERVER cancelled /o;
+    }
+    # rejecting[perl]
+    if ($left =~ /^rejecting\[perl\] <[^>]+> \d+ (.*)/o) {
+      $innd_filter_perl{$1}++;
+      return 1;
+    }
+    # closed lost
+    return 1 if $left =~ /^\S+ closed lost \d+/o;
+    # control command (by letter)
+    if ($left =~ /^(\w)$/o) {
+      my $command = $1;
+      my $cmd = $ctlinnd{$command};
+      $cmd = $command unless $cmd;
+      return 1 if $cmd eq 'flush'; # to avoid a double count
+      $innd_control{"$cmd"}++;
+      return 1;
+    }
+    # control command (letter + reason)
+    if ($left =~ /^(\w):.*$/o) {
+      my $command = $1;
+      my $cmd = $ctlinnd{$command};
+      $cmd = $command unless $cmd;
+      return 1 if $cmd eq 'flush'; # to avoid a double count
+      $innd_control{"$cmd"}++;
+      return 1;
+    }
+    # opened
+    return 1 if $left =~ /\S+ opened \S+:\d+:file$/o;
+    # buffered
+    return 1 if $left =~ /\S+ buffered$/o;
+    # spawned
+    return 1 if $left =~ /\S+ spawned \S+:\d+:proc:\d+$/o;
+    return 1 if $left =~ /\S+ spawned \S+:\d+:file$/o;
+    # running
+    return 1 if $left =~ /\S+ running$/o;
+    # sleeping
+    if ($left =~ /(\S+):\d+:proc:\d+ sleeping$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_blocked{$server}++;
+      return 1;
+    }
+    # blocked sleeping
+    if ($left =~ /(\S+):\d+:proc:\d+ blocked sleeping/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_blocked{$server}++;
+      return 1;
+    }
+    if ($left =~ /(\S+):\d+ blocked sleeping/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_blocked{$server}++;
+      return 1;
+    }
+    # restarted
+    return 1 if $left =~ m/^\S+ restarted$/o;
+    # starting
+    return 1 if $left =~ m/^\S+ starting$/o;
+    # readclose
+    return 1 if $left =~ m/^\S+:\d+ readclose+$/o;
+    # rejected 502
+    if ($left =~ m/^(\S+) rejected 502$/) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_no_permission{$server}++;
+      return 1;
+    }
+    # rejected 505
+    if ($left =~ m/^(\S+) rejected 505$/) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_too_many_connects_per_minute{$server}++;
+      return 1;
+    }
+    # connected
+    if ($left =~ /^(\S+) connected \d+/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_connect{$server}++;
+      return 1;
+    }
+    # closed (with times)
+    if ($left =~ /(\S+):\d+ closed seconds (\d+) accepted (\d+) refused (\d+) rejected (\d+) duplicate (\d+) accepted size (\d+) duplicate size (\d+)$/o) {
+      my ($server, $seconds, $accepted, $refused, $rejected, $duplicate, $accptsize, $dupsize) =
+	($1, $2, $3, $4, $5, $6, $7, $8);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_seconds{$server} += $seconds;
+      $innd_accepted{$server} += $accepted;
+      $innd_refused{$server} += $refused;
+      $innd_rejected{$server} += $rejected;
+      $innd_stored_size{$server} += $accptsize;
+      $innd_duplicated_size{$server} += $dupsize;
+      return 1;
+    } elsif ($left =~ /(\S+):\d+ closed seconds (\d+) accepted (\d+) refused (\d+) rejected (\d+)$/o) {
+      # closed (with times)
+      my ($server, $seconds, $accepted, $refused, $rejected) =
+	($1, $2, $3, $4, $5);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_seconds{$server} += $seconds;
+      $innd_accepted{$server} += $accepted;
+      $innd_refused{$server} += $refused;
+      $innd_rejected{$server} += $rejected;
+      return 1;
+    }
+    # closed (without times (?))
+    return 1 if $left =~ m/\S+ closed$/o;
+    # checkpoint
+    return 1 if $left =~ m/^\S+:\d+ checkpoint /o;
+    # if ($left =~ /(\S+):\d+ checkpoint seconds (\d+) accepted (\d+)
+    #     refused (\d+) rejected (\d+)$/) {
+    #   # Skipped...
+    #   my ($server, $seconds, $accepted, $refused, $rejected) =
+    #      ($1, $2, $3, $4, $5);
+    #   $innd_seconds{$server} += $seconds;
+    #   $innd_accepted{$server} += $accepted;
+    #   $innd_refused{$server} += $refused;
+    #   $innd_rejected{$server} += $rejected;
+    #   return 1;
+    # }
+
+    # flush
+    if ($left =~ /(\S+) flush$/o) {
+      $innd_control{"flush"}++;
+      return 1;
+    }
+    # flush-file
+    if ($left =~ /flush_file/) {
+       $innd_control{"flush_file"}++;
+       return 1;
+     }
+    # too many connections from site
+    if ($left =~ /too many connections from (\S+)/o) {
+      $innd_max_conn{$1}++;
+      return 1;
+    }
+    # overview exit 0 elapsed 23 pid 28461
+    return 1 if $left =~ m/\S+ exit \d+ .*$/o;
+    # internal rejecting huge article
+    if ($left =~ /(\S+) internal rejecting huge article/o) {
+      my $server = $1;
+      $server =~ s/:\d+$//o;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_huge{$server}++;
+      return 1;
+    }
+    # internal closing free channel
+    if ($left =~ /(\S+) internal closing free channel/o) {
+      $innd_misc{"Free channel"}++;
+      return 1;
+    }
+    # internal (other)
+    return 1 if $left =~ /\S+ internal/o;
+    # wakeup
+    return 1 if $left =~ /\S+ wakeup$/o;
+    # throttle
+    if ($left =~ /(\S+) throttled? /) {
+      $innd_control{"throttle"}++;
+      return 1;
+    }
+    # profile timer
+    # ME time X nnnn X(X) [...]
+    # The exact timers change from various versions of INN, so try to deal
+    # with this in a general fashion.
+    if ($left =~ m/^\S+\s+                         # ME
+	           time\ (\d+)\s+                  # time
+                   ((?:\S+\ \d+\(\d+\)\s*)+)       # timer values
+                   $/ox) {
+      $innd_time_times += $1;
+      my $timers = $2;
+
+      while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
+        my $name = $timer_names{$1} || $1;
+        my $average = $2 / ($3 || 1);
+        $innd_time_time{$name} += $2;
+        $innd_time_num{$name} += $3;
+        $innd_time_min{$name} = $average
+          if ($3 && $innd_time_min{$name} > $average);
+        $innd_time_max{$name} = $average
+          if ($3 && $innd_time_max{$name} < $average);
+      }
+      return 1;
+    }
+    # ME time xx idle xx(xx)     [ bug ? a part of timer ?]
+    return 1 if $left =~ m/^ME time \d+ idle \d+\(\d+\)\s*$/o;
+    # ME HISstats x hitpos x hitneg x missed x dne
+    #
+    # from innd/his.c:
+    # HIShitpos: the entry existed in the cache and in history.
+    # HIShitneg: the entry existed in the cache but not in history.
+    # HISmisses: the entry was not in the cache, but was in the history file.
+    # HISdne:    the entry was not in cache or history.
+    if ($left =~ m/^ME\ HISstats                  # ME HISstats
+	           \ (\d+)\s+hitpos               # hitpos
+	           \ (\d+)\s+hitneg               # hitneg
+	           \ (\d+)\s+missed               # missed
+                   \ (\d+)\s+dne                  # dne
+	           $/ox) {
+      $innd_his{'Positive hits'} += $1;
+      $innd_his{'Negative hits'} += $2;
+      $innd_his{'Cache misses'}  += $3;
+      $innd_his{'Do not exist'}  += $4;
+      return 1;
+    }
+    # SERVER history cache final: 388656 lookups, 1360 hits
+    if ($left =~ m/^SERVER history cache final: (\d+) lookups, (\d+) hits$/) {
+      $innd_cache{'Lookups'} += $1;
+      $innd_cache{'Hits'}    += $2;
+      return 1;
+    }
+    # added by Md 20000122
+    return 1 if $left =~ m/^Pre-commit cache initialized/;
+    # bad_hosts (appears after a "cant gesthostbyname" from a feed)
+    return 1 if $left =~ m/\S+ bad_hosts /o;
+    # cant read
+    return 1 if $left =~ m/\S+ cant read/o;
+    # cant write
+    return 1 if $left =~ m/\S+ cant write/o;
+    # cant flush
+    return 1 if $left =~ m/\S+ cant flush/o;
+    # spoolwake
+    return 1 if $left =~ m/\S+ spoolwake$/o;
+    # spooling
+    return 1 if $left =~ m/\S+ spooling/o;
+    # DEBUG
+    return 1 if $left =~ m/^DEBUG /o;
+    # NCmode
+    return 1 if $left =~ m/\S+ NCmode /o;
+    # outgoing
+    return 1 if $left =~ m/\S+ outgoing/o;
+    # inactive
+    return 1 if $left =~ m/\S+ inactive/o;
+    # timeout
+    return 1 if $left =~ m/\S+ timeout/o;
+    # lcsetup
+    return 1 if $left =~ m/\S+ lcsetup/o;
+    # rcsetup
+    return 1 if $left =~ m/\S+ rcsetup/o;
+    # flush_all
+    return 1 if $left =~ m/\S+ flush_all/o;
+    # buffered
+    return 1 if $left =~ m/\S+ buffered$/o;
+    # descriptors
+    return 1 if $left =~ m/\S+ descriptors/o;
+    # ccsetup
+    return 1 if $left =~ m/\S+ ccsetup/o;
+    # renumbering
+    return 1 if $left =~ m/\S+ renumbering/o;
+    # renumber
+    return 1 if $left =~ m/\S+ renumber /o;
+    # ihave from me
+    if ($left =~ m/\S+ ihave_from_me /o) {
+      $controlchan_ihave_site{'ME'}++;
+      return 1;
+    }
+    # sendme from me
+    if ($left =~ m/\S+ sendme_from_me /o) {
+      $controlchan_sendme_site{'ME'}++;
+      return 1;
+    }
+    # newgroup
+    if ($left =~ m/\S+ newgroup (\S+) as (\S)/o) {
+      $innd_newgroup{$1} = $2;
+      return 1;
+    }
+    # rmgroup
+    if ($left =~ m/\S+ rmgroup (\S+)$/o) {
+      $innd_rmgroup{$1}++;
+      return 1;
+    }
+    # changegroup
+    if ($left =~ m/\S+ change_group (\S+) to (\S)/o) {
+      $innd_changegroup{$1} = $2;
+      return 1;
+    }
+    # paused
+    if ($left =~ m/(\S+) paused /o) {
+      $innd_control{"paused"}++;
+      return 1;
+    }
+    # throttled
+    return 1 if $left =~ m/\S+ throttled/o;
+    # reload
+    if ($left =~ m/(\S+) reload/o) {
+      $innd_control{"reload"}++;
+      return 1;
+    }
+    # shutdown
+    if ($left =~ m/(\S+) shutdown/o) {
+      $innd_control{"shutdown"}++;
+      return 1;
+    }
+    # SERVER servermode paused
+    return 1 if ($left =~ /(\S+) servermode paused$/o);
+    # SERVER servermode running
+    return 1 if ($left =~ /(\S+) servermode running$/o);
+    # SERVER flushlogs paused
+    if ($left =~ /(\S+) flushlogs /) {
+      $innd_control{"flushlogs"}++;
+      return 1;
+    }
+    # think it's a dotquad
+    return 1 if $left =~ /think it\'s a dotquad: /o;
+    # bad_ihave
+    if ($left =~ /(\S+) bad_ihave /) {
+      my $server = $1;
+      $server =~ s/:\d+$//o;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_bad_ihave{$server}++;
+      return 1;
+    }
+    # bad_messageid
+    if ($left =~ /(\S+) bad_messageid/o) {
+      my $server = $1;
+      $server =~ s/:\d+$//o;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_bad_msgid{$server}++;
+      return 1;
+    }
+    # bad_sendme
+    if ($left =~ /(\S+) bad_sendme /o) {
+      my $server = $1;
+      $server =~ s/:\d+$//o;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_bad_sendme{$server}++;
+      return 1;
+    }
+    # bad_command
+    if ($left =~ /(\S+) bad_command /o) {
+      my $server = $1;
+      $server =~ s/:\d+$//o;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innd_bad_command{$server}++;
+      return 1;
+    }
+    # bad_newsgroup
+    if ($left =~ /(\S+) bad_newsgroup /o) {
+      my $server = $1;
+      $server =~ s/:\d+$//o;
+      $innd_bad_newsgroup{$server}++;
+      $server = lc $server unless $CASE_SENSITIVE;
+      return 1;
+    }
+    if ($left =~ m/ cant /o) {
+      # cant select Bad file number
+      if ($left =~ / cant select Bad file number/o) {
+	$innd_misc{"Bad file number"}++;
+	return 1;
+      }
+      # cant gethostbyname
+      if ($left =~ / cant gethostbyname/o) {
+	$innd_misc{"gethostbyname error"}++;
+	return 1;
+      }
+      # cant accept RCreader
+      if ($left =~ / cant accept RCreader /o) {
+	$innd_misc{"RCreader"}++;
+	return 1;
+      }
+      # cant sendto CCreader
+      if ($left =~ / cant sendto CCreader /o) {
+	$innd_misc{"CCreader"}++;
+	return 1;
+      }
+      # cant (other) skipped - not particularly interesting
+      return 1;
+    }
+    # bad_newsfeeds no feeding sites
+    return 1 if $left =~ /\S+ bad_newsfeeds no feeding sites/o;
+    # CNFS-sm: cycbuff rollover - possibly interesting
+    return 1 if $left =~ /CNFS-sm: cycbuff \S+ rollover to cycle/o;
+    # CNFS-sm: CNFSflushallheads: flushing - possibly interesting
+    return 1 if $left =~ /CNFS-sm: CNFSflushallheads: flushing /o;
+    # CNFS-sm: metacycbuff rollover with SEQUENTIAL
+    return 1 if $left =~ /CNFS-sm: metacycbuff \S+ cycbuff is moved to /o;
+    # Cleanfeed status reports
+    return 1 if $left =~ /^filter: status/o;
+  }
+  ########
+  ## innfeed
+  if ($prog eq "innfeed") {
+    # connected
+    if ($left =~ /(\S+):\d+ connected$/) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innfeed_connect{$server}++;
+      return 1;
+    }
+    # closed periodic
+    return 1 if $left =~ m/\S+:\d+ closed periodic$/o;
+    # periodic close
+    return 1 if $left =~ m/\S+:\d+ periodic close$/o;
+    # final (child)
+    return 1 if $left =~ m/\S+:\d+ final seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+/o;
+    # global (real)
+    return 1 if $left =~ m/\S+ global seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+ missing \d+/o;
+    # final (real) (new format)
+    if ($left =~ /(\S+) final seconds (\d+) offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) accsize (\d+) rejsize (\d+) spooled (\d+)/o) {
+      my ($server, $seconds, $offered, $accepted, $refused, $rejected,
+	  $missing, $accepted_size, $rejected_size, $spooled) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innfeed_seconds{$server} += $seconds;
+      $innfeed_offered{$server} += $offered;
+      $innfeed_accepted{$server} += $accepted;
+      $innfeed_refused{$server} += $refused;
+      $innfeed_rejected{$server} += $rejected;
+      $innfeed_missing{$server} += $missing;
+      $innfeed_spooled{$server} += $spooled;
+      $innfeed_accepted_size{$server} += $accepted_size;
+      $innfeed_rejected_size{$server} += $rejected_size;
+      return 1;
+    } elsif ($left =~ /(\S+) final seconds (\d+) offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) spooled (\d+)/o) {
+      my ($server, $seconds, $offered, $accepted, $refused, $rejected,
+	  $missing, $spooled) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innfeed_seconds{$server} += $seconds;
+      $innfeed_offered{$server} += $offered;
+      $innfeed_accepted{$server} += $accepted;
+      $innfeed_refused{$server} += $refused;
+      $innfeed_rejected{$server} += $rejected;
+      $innfeed_missing{$server} += $missing;
+      $innfeed_spooled{$server} += $spooled;
+      return 1;
+    }
+    # final (only seconds & spooled)
+    if ($left =~ /(\S+) final seconds (\d+) spooled (\d+)/o) {
+      my ($server, $seconds, $spooled) = ($1, $2, $3);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innfeed_seconds{$server} += $seconds;
+      $innfeed_spooled{$server} += $spooled;
+      return 1;
+    }
+    # checkpoint
+    return 1 if $left =~ m/\S+ checkpoint seconds/o;
+    # ME file xxxx shrunk from yyyy to zzz
+    if ($left =~ /^ME file (.*)\.output shrunk from (\d+) to (\d+)$/) {
+      my ($file, $s1, $s2) = ($1, $2, $3);
+      $file =~ s|^.*/([^/]+)$|$1|; # keep only the server name
+      $innfeed_shrunk{$file} += $s1 - $s2;
+      return 1;
+    }
+    # profile timer
+    # ME time X nnnn X(X) [...]
+    return 1 if $left =~ m/backlogstats/;
+    if ($left =~ m/^\S+\s+                         # ME
+                   time\ (\d+)\s+                  # time
+                   ((?:\S+\ \d+\(\d+\)\s*)+)       # timer values
+                   $/ox) {
+      $innfeed_time_times += $1;
+      my $timers = $2;
+
+      while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
+        my $name = $innfeed_timer_names{$1} || $1;
+        my $average = $2 / ($3 || 1);
+        $innfeed_time_time{$name} += $2;
+        $innfeed_time_num{$name} += $3;
+        $innfeed_time_min{$name} = $average
+          if ($3 && $innfeed_time_min{$name} > $average);
+        $innfeed_time_max{$name} = $average
+          if ($3 && $innfeed_time_max{$name} < $average);
+      }
+      return 1;
+    }
+    # xxx grabbing external tape file
+    return 1 if $left =~ m/ grabbing external tape file/o;
+    # hostChkCxns - maxConnections was
+    return 1 if $left =~ m/hostChkCxns - maxConnections was /o;
+    # cxnsleep
+    return 1 if $left =~ m/\S+ cxnsleep .*$/o;
+    # idle
+    return 1 if $left =~ m/\S+ idle tearing down connection$/o;
+    # remote
+    return 1 if $left =~ m/\S+ remote .*$/o;
+    # spooling
+    return 1 if $left =~ m/\S+ spooling no active connections$/o;
+    # ME articles total
+    return 1 if $left =~ m/(?:SERVER|ME) articles total \d+ bytes \d+/o;
+    # ME articles active
+    return 1 if $left =~ m/(?:SERVER|ME) articles active \d+ bytes \d+/o;
+    # connect : Connection refused
+    return 1 if $left =~ m/connect : Connection refused/o;
+    # connect : Network is unreachable
+    return 1 if $left =~ m/connect : Network is unreachable/o;
+    # connect : Address family not supported by protocol
+    return 1 if $left =~ m/connect : Address family not supported by protocol/o;
+    # connect : No route to host
+    return 1 if $left =~ m/connect : No route to host/o;
+    # connection vanishing
+    return 1 if $left =~ m/connection vanishing/o;
+    # can't resolve hostname
+    return 1 if $left =~ m/can\'t resolve hostname/o;
+    # new hand-prepared backlog file
+    return 1 if $left =~ m/new hand-prepared backlog file/o;
+    # flush re-connect failed
+    return 1 if $left =~ m/flush re-connect failed/o;
+    # internal QUIT while write pending
+    return 1 if $left =~ m/internal QUIT while write pending/o;
+    # ME source lost . Exiting
+    return 1 if $left =~ m/(?:SERVER|ME) source lost . Exiting/o;
+    # ME starting innfeed (+version & date)
+    return 1 if $left =~ m/(?:SERVER|ME) starting innfeed/o;
+    # ME finishing at (date)
+    return 1 if $left =~ m/(?:SERVER|ME) finishing at /o;
+    # mode no-CHECK entered
+    return 1 if $left =~ m/mode no-CHECK entered/o;
+    # mode no-CHECK exited
+    return 1 if $left =~ m/mode no-CHECK exited/o;
+    # closed
+    return 1 if $left =~ m/^(\S+) closed$/o;
+    # global (+ seconds offered accepted refused rejected missing)
+    return 1 if $left =~ m/^(\S+) global/o;
+    # idle connection still has articles
+    return 1 if $left =~ m/^(\S+) idle connection still has articles$/o;
+    # missing article for IHAVE-body
+    return 1 if $left =~ m/^(\S+) missing article for IHAVE-body$/o;
+    # cannot continue
+    return 1 if $left =~ m/^cannot continue/o;
+    if ($left =~ /^(?:SERVER|ME)/o) {
+      # ME dropping articles into ...
+      return 1 if $left = ~/ dropping articles into /o;
+      # ME dropped ...
+      return 1 if $left = ~/ dropped /o;
+      # ME internal bad data in checkpoint file
+      return 1 if $left =~ m/ internal bad data in checkpoint/o;
+      # ME two filenames for same article
+      return 1 if $left =~ m/ two filenames for same article/o;
+      # ME unconfigured peer
+      return 1 if $left =~ m/ unconfigured peer/o;
+      # exceeding maximum article size
+      return 1 if $left =~ m/ exceeding maximum article byte/o;
+      # no space left on device errors
+      return 1 if $left =~ m/ ioerr fclose/o;
+      return 1 if $left =~ m/ lock failed for host/o;
+      return 1 if $left =~ m/ lock file pid-write/o;
+      return 1 if $left =~ m/ locked cannot setup peer/o;
+      return 1 if $left =~ m/ received shutdown signal/o;
+      # unconfigured peer
+      return 1 if $left =~ m/ unconfigured peer/o;
+      # ME lock
+      return 1 if $left =~ m/ lock/o;
+      # ME exception: getsockopt (0): Socket operation on non-socket
+      return 1 if $left =~ m/ exception: getsockopt /o;
+      # ME config aborting fopen (...) Permission denied
+      return 1 if $left =~ m/ config aborting fopen /o;
+      # ME cant chmod innfeed.pid....
+      return 1 if $left =~ m/ cant chmod \S+\/innfeed.pid/o;
+      return 1 if $left =~ m/ tape open failed /o;
+      return 1 if $left =~ m/ oserr open checkpoint file:/o;
+      # ME finishing (quickly)
+      return 1 if $left =~ m/\(quickly\) /o;
+      # ME config: value of streaming is not a boolean
+      return 1 if $left =~ m/config: value of \S+ is not/o;
+    }
+    # hostChkCxn - now: x.xx, prev: x.xx, abs: xx, curr: x
+    return 1 if $left =~ m/ hostChkCxn - now/o;
+    # loading path_to_config_file/innfeed.conf
+    return 1 if $left =~ m/loading /o;
+    # Finnaly, to avoid problems with strange error lines, ignore them.
+    #return 1 if ($left =~ /ME /);
+  }
+  ########
+  ## innxmit
+  if ($prog eq "innxmit") {
+    # 437 Duplicate article
+    if ($left =~ /(\S+) rejected [^\s]+ \(.*?\) 437 Duplicate article$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_duplicate{$server}++;
+      return 1;
+    }
+    # 437 Unapproved for
+    if ($left =~ /(\S+) rejected [^\s]+ \(.*\) 437 Unapproved for \"(.*?)\"$/o) {
+      my ($server, $group) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_unapproved{$server}++;
+      $innxmit_unapproved_g{$group}++;
+      return 1;
+    }
+    # 437 Too old -- ...
+    if ($left =~ /(\S+) rejected [^\s]+ \(.*\) 437 Too old -- \".*?\"$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_tooold{$server}++;
+      return 1;
+    }
+    # 437 Unwanted site ... in path
+    if ($left =~
+      /(\S+) rejected [^\s]+ \(.*?\) 437 Unwanted site (\S+) in path$/o) {
+      my ($server, $site) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_uw_site{$server}++;
+      # $innxmit_site_path{$site}++;
+      return 1;
+    }
+    # 437 Unwanted newsgroup "..."
+    if ($left =~
+      /(\S+) rejected [^\s]+ \(.*?\) 437 Unwanted newsgroup \"(\S+)\"$/o) {
+      my ($server, $group) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_uw_ng_s{$server}++;
+      $innxmit_uw_ng{$group}++;
+      return 1;
+    }
+    # 437 Unwanted distribution "..."
+    if ($left =~
+      /(\S+) rejected [^\s]+ \(.*?\) 437 Unwanted distribution \"(\S+)\"$/o) {
+      my ($server, $dist) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_uw_dist_s{$server}++;
+      $innxmit_uw_dist{$dist}++;
+      return 1;
+    }
+    # xx rejected foo.bar/12345 (foo/bar/12345) 437 Unwanted distribution "..."
+    if ($left =~ /^(\S+) rejected .* 437 Unwanted distribution \"(\S+)\"$/o) {
+      my ($server, $dist) = ($1, $2);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_uw_dist_s{$server}++;
+      $innxmit_uw_dist{$dist}++;
+      return 1;
+    }
+    # 437 Linecount x != y +- z
+    if ($left =~ /(\S+) rejected [^\s]+ \(.*?\) 437 Linecount/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_badart{$server}++;
+      $innxmit_linecount{$server}++;
+      return 1;
+    }
+    # 437 Newsgroup name illegal -- "xxx"
+    if ($left =~ /(\S+) rejected .* 437 Newsgroup name illegal -- "[^\"]*"$/) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_others{$server}++;
+      $innxmit_badart{$server}++;
+      return 1;
+    }
+    # Streaming retries
+    return 1 if ($left =~ /\d+ Streaming retries$/o);
+    # ihave failed
+    if ($left =~ /(\S+) ihave failed/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_ihfail{$server} = 1;
+      if ($left = /436 \S+ NNTP \S+ out of space/o) {
+	$innxmit_nospace{$server}++;
+	return 1;
+      }
+      if ($left = /400 \S+ space/o) {
+	$innxmit_nospace{$server}++;
+	return 1;
+      }
+      if ($left = /400 Bad file/o) {
+	$innxmit_crefused{$server}++;
+	return 1;
+      }
+      if ($left = /480 Transfer permission denied/o) {
+	$innxmit_crefused{$server}++;
+	return 1;
+      }
+    }
+    # stats (new format)
+    if ($left =~
+      /(\S+) stats offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) accsize (\d+) rejsize (\d+)$/o) {
+      my ($server, $offered, $accepted, $refused, $rejected, $missing, $accbytes, $rejbytes) =
+	($1, $2, $3, $4, $5, $6, $7, $8);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_offered{$server} += $offered;
+      $innxmit_offered{$server} -= $innxmit_ihfail{$server}
+        if ($innxmit_ihfail{$server});
+      $innxmit_accepted{$server} += $accepted;
+      $innxmit_refused{$server} += $refused;
+      $innxmit_rejected{$server} += $rejected;
+      $innxmit_missing{$server} += $missing;
+      $innxmit_accepted_size{$server} += $accbytes;
+      $innxmit_rejected_size{$server} += $rejbytes;
+      $innxmit_site{$server}++;
+      $innxmit_ihfail{$server} = 0;
+      return 1;
+    }
+    # stats
+    if ($left =~
+      /(\S+) stats offered (\d+) accepted (\d+) refused (\d+) rejected (\d+)$/o) {
+      my ($server, $offered, $accepted, $refused, $rejected) =
+	($1, $2, $3, $4, $5);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_offered{$server} += $offered;
+      $innxmit_offered{$server} -= $innxmit_ihfail{$server}
+        if ($innxmit_ihfail{$server});
+      $innxmit_accepted{$server} += $accepted;
+      $innxmit_refused{$server} += $refused;
+      $innxmit_rejected{$server} += $rejected;
+      $innxmit_site{$server}++;
+      $innxmit_ihfail{$server} = 0;
+      return 1;
+    }
+    # times
+    if ($left =~ /(\S+) times user (\S+) system (\S+) elapsed (\S+)$/o) {
+      my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_times{$server} += $elapsed;
+      return 1;
+    }
+    # connect & no space
+    if ($left =~ /(\S+) connect \S+ 400 No space/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_nospace{$server}++;
+      $innxmit_site{$server}++;
+      return 1;
+    }
+    # connect & NNTP no space
+    if ($left =~ /(\S+) connect \S+ 400 \S+ out of space/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_nospace{$server}++;
+      $innxmit_site{$server}++;
+      return 1;
+    }
+    # connect & loadav
+    if ($left =~ /(\S+) connect \S+ 400 loadav/o) {
+      my $server = $1;
+      if ($left =~ /expir/i) {
+	$server = lc $server unless $CASE_SENSITIVE;
+	$innxmit_expire{$server}++;
+	$innxmit_site{$server}++;
+	return 1;
+      }
+    }
+    # connect 400 (other)
+    if ($left =~ /(\S+) connect \S+ 400/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_crefused{$server}++;
+      $innxmit_site{$server}++;
+      return 1;
+    }
+    # connect failed
+    if ($left =~ /(\S+) connect failed/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_cfail_host{$server}++;
+      $innxmit_site{$server}++;
+      return 1;
+    }
+    # authenticate failed
+    if ($left =~ /(\S+) authenticate failed/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_afail_host{$server}++;
+      $innxmit_site{$server}++;
+      return 1;
+    }
+    # xxx ihave failed 400 loadav [innwatch:hiload] yyy gt zzz
+    if ($left =~ /^(\S+) ihave failed 400 loadav/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $innxmit_hiload{$server}++;
+      return 1;
+    }
+    # ihave failed
+    return 1 if ($left =~ /\S+ ihave failed/o);
+    # requeued (....) 436 No space
+    return 1 if ($left =~ /\S+ requeued \S+ 436 No space/o);
+    # requeued (....) 400 No space
+    return 1 if ($left =~ /\S+ requeued \S+ 400 No space/o);
+    # requeued (....) 436 Can't write history
+    return 1 if ($left =~ /\S+ requeued \S+ 436 Can\'t write history/o);
+    # unexpected response code
+    return 1 if ($left =~ /unexpected response code /o);
+  }
+
+  ########
+  ## nntplink
+  if ($prog eq "nntplink") {
+    $left =~ s/^(\S+):/$1/;
+    # EOF
+    if ($left =~ /(\S+) EOF /o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_eof{$server}++;
+      return 1;
+    }
+    # Broken pipe
+    if ($left =~ /(\S+) Broken pipe$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_bpipe{$server}++;
+      return 1;
+    }
+    # already running - won't die
+    return 1 if $left =~ /\S+ nntplink.* already running /o;
+    # connection timed out
+    if ($left =~ /(\S+) connection timed out/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_bpipe{$server}++;
+      return 1;
+    }
+    # greeted us with 400 No space
+    if ($left =~ /(\S+) greeted us with 400 No space/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_nospace{$server}++;
+      return 1;
+    }
+    # greeted us with 400 loadav
+    if ($left =~ /(\S+) greeted us with 400 loadav/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_hiload{$server}++;
+      return 1;
+    }
+    # greeted us with 400 (other)
+    if ($left =~ /(\S+) greeted us with 400/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      if ($left =~ /expir/i) {
+	$nntplink_expire{$server}++;
+      } else {
+	$nntplink_fail{$server}++;
+      }
+      return 1;
+    }
+    # greeted us with 502
+    if ($left =~ /(\S+) greeted us with 502/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_auth{$server}++;
+      return 1;
+    }
+    # sent authinfo
+    if ($left =~ /(\S+) sent authinfo/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_auth{$server}++;
+      return 1;
+    }
+    # socket()
+    if ($left =~ /(\S+) socket\(\): /o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_sockerr{$server}++;
+      return 1;
+    }
+    # select()
+    if ($left =~ /(\S+) select\(\) /o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_site{$server}++;
+      $nntplink_selecterr{$server}++;
+      return 1;
+    }
+    # sent IHAVE
+    if ($left =~ /(\S+) sent IHAVE/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_ihfail{$server}++;
+      if (($left =~ / 436 /) && ($left =~ / out of space /)) {
+	$nntplink_fake_connects{$server}++;
+	$nntplink_nospace{$server}++;
+      }
+      return 1;
+    }
+    # article .... failed(saved): 436 No space
+    if ($left =~ /(\S+) .* failed\(saved\): 436 No space$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_nospace{$server}++;
+      return 1;
+    }
+    # article .. 400 No space left on device writing article file -- throttling
+    if ($left =~ /(\S+) .* 400 No space left on device writing article file -- throttling$/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_nospace{$server}++;
+      return 1;
+    }
+    # stats
+    if ($left =~ /(\S+) stats (\d+) offered (\d+) accepted (\d+) rejected (\d+) failed (\d+) connects$/o) {
+      my ($server, $offered, $accepted, $rejected, $failed, $connects) =
+	($1, $2, $3, $4, $5, $6);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_offered{$server} += $offered - $nntplink_ihfail{$server}++;
+      $nntplink_accepted{$server} += $accepted;
+      $nntplink_rejected{$server} += $rejected;
+      $nntplink_failed{$server} += $failed;
+      $nntplink_connects{$server} += $connects;
+      $nntplink_ihfail{$server} = 0;
+      if ($nntplink_fake_connects{$server}) {
+	$nntplink_site{$server} += $nntplink_fake_connects{$server};
+	$nntplink_fake_connects{$server} = 0;
+      } else {
+	$nntplink_site{$server}++;
+      }
+      return 1;
+    }
+    # xmit
+    if ($left =~ /(\S+) xmit user (\S+) system (\S+) elapsed (\S+)$/o) {
+      my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_times{$server} += $elapsed;
+      return 1;
+    }
+    # xfer
+    return 1 if $left =~ /\S+ xfer/o;
+    # Links down .. x hours
+    if ($left =~ /(\S+) Links* down \S+ \d+/o) {
+      # Collected but not used
+      # my $server = $1;
+      # $server = lc $server unless $CASE_SENSITIVE;
+      # $nntplink_down{$server} += $hours;
+      return 1;
+    }
+    # 503 Timeout
+    if ($left =~ /^(\S+) \S+ \S+ \S+ 503 Timeout/o) {
+      # Collected but not used
+      # my $server = $1;
+      # $server = lc $server unless $CASE_SENSITIVE;
+      # $nntplink_timeout{$server}++;
+      return 1;
+    }
+    # read() error while reading reply
+    if ($left =~ /^(\S+): read\(\) error while reading reply/o) {
+      my $server = $1;
+      $server = lc $server unless $CASE_SENSITIVE;
+      $nntplink_failed{$server}++;
+      return 1;
+    }
+    # Password file xxxx not found
+    return 1 if $left =~ /^\S+ Password file \S+ not found/;
+    # No such
+    return 1 if $left =~ /^\S+ \S+ \S+ No such/;
+    # already running
+    return 1 if $left =~ /^\S+ \S+ already running/;
+    # error reading version from datafile
+    return 1 if $left =~ /error reading version from datafile/;
+  }
+  ########
+  ## nnrpd
+  if ($prog eq "nnrpd")
+  {
+    # Fix a small bug of nnrpd (inn 1.4*)
+    $left =~ s/^ /\? /o;
+    # Another bug (in INN 1.5b1)
+    return 1 if $left =~ /^\020\002m$/o; # ^P^Bm
+    # bad_history at num for <ref>
+    return 1 if $left =~ /bad_history at \d+ for /o;
+    # timeout short
+    return 1 if $left =~ /\S+ timeout short$/o;
+    # < or > + (blablabla)
+    return 1 if $left =~ /^\S+ [\<\>] /o;
+    # cant opendir ... I/O error
+    return 1 if $left =~ /\S+ cant opendir \S+ I\/O error$/o;
+    # perl filtering enabled
+    return 1 if $left =~ /perl filtering enabled$/o;
+    # connect
+    if ($left =~ /(\S+) (\([0-9a-fA-F:.]*\) )?connect$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_connect{$dom}++;
+      $nnrpd_connect{$cust}++;
+      return 1;
+    }
+    # group
+    if ($left =~ /(\S+) group (\S+) (\d+)$/o) {
+      my ($cust, $group, $num) = ($1, $2, $3);
+      if ($num) {
+	$nnrpd_group{$group} += $num;
+	my ($hierarchy) = $group =~ /^([^\.]+).*$/o;
+	$nnrpd_hierarchy{$hierarchy} += $num;
+      }
+      return 1;
+    }
+    # post failed
+    if ($left =~ /(\S+) post failed (.*)$/o) {
+      my ($cust, $error) = ($1, $2);
+      $nnrpd_post_error{$error}++;
+      return 1;
+    }
+    # post ok
+    return 1 if $left =~ /\S+ post ok/o;
+    # posts
+    if ($left =~ /(\S+) posts received (\d+) rejected (\d+)$/o) {
+      my ($cust, $received, $rejected) = ($1, $2, $3);
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_post_ok{$dom} += $received;
+      $nnrpd_dom_post_rej{$dom} += $rejected;
+      $nnrpd_post_ok{$cust} += $received;
+      $nnrpd_post_rej{$cust} += $rejected;
+      return 1;
+    }
+    # noperm post without permission
+    if ($left =~ /(\S+) noperm post without permission/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_post_rej{$dom} ++;
+      $nnrpd_post_rej{$cust} ++;
+      return 1;
+    }
+    # no_permission
+    if ($left =~ /(\S+) no_(permission|access)$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_no_permission{$cust}++;
+      $nnrpd_dom_no_permission{$dom}++;
+      return 1;
+    }
+    # bad_auth
+    if ($left =~ /(\S+) bad_auth$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_no_permission{$dom}++;
+      $nnrpd_no_permission{$cust}++;
+      return 1;
+    }
+    # authinfo
+    if ($left =~ /\S+ user (\S+)$/o) {
+      my $user = $1;
+      $nnrpd_auth{$user}++;
+      return 1;
+    }
+    # unrecognized + command
+    if ($left =~ /(\S+) unrecognized (.*)$/o) {
+      my ($cust, $error) = ($1, $2);
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $error = "_null command_" if ($error !~ /\S/);
+      $error =~ s/^(xmotd) .*$/$1/i if ($error =~ /^xmotd .*$/i);
+      $nnrpd_dom_unrecognized{$dom}++;
+      $nnrpd_unrecognized{$cust}++;
+      $nnrpd_unrecogn_cmd{$error}++;
+      return 1;
+    }
+    # exit
+    if ($left =~ /(\S+) exit articles (\d+) groups (\d+)$/o) {
+      my ($cust, $articles, $groups) = ($1, $2, $3);
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust) || '?';
+      $nnrpd_connect{$cust}++, $nnrpd_dom_connect{$dom}++ if $cust eq '?';
+      $nnrpd_groups{$cust} += $groups;
+      $nnrpd_dom_groups{$dom} += $groups;
+      $nnrpd_articles{$cust} += $articles;
+      $nnrpd_dom_articles{$dom} += $articles;
+      return 1;
+    }
+    # times
+    if ($left =~ /(\S+) times user (\S+) system (\S+) idle (\S+) elapsed (\S+)$/o) {
+      my ($cust, $user, $system, $idle, $elapsed) = ($1, $2, $3, $4, $5);
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_times{$cust} += $elapsed;
+      $nnrpd_resource_user{$cust} += $user;
+      $nnrpd_resource_system{$cust} += $system;
+      $nnrpd_resource_idle{$cust} += $idle;
+      $nnrpd_resource_elapsed{$cust} += $elapsed;
+      $nnrpd_dom_times{$dom} += $elapsed;
+      return 1;
+    }
+    # artstats
+    if ($left =~ /(\S+) artstats get (\d+) time (\d+) size (\d+)$/o) {
+      my ($cust, $articles, $time, $bytes) = ($1, $2, $3, $4);
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_bytes{$cust} += $bytes;
+      $nnrpd_dom_bytes{$dom} += $bytes;
+      return 1;
+    }
+    # timeout
+    if ($left =~ /(\S+) timeout$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_timeout{$dom}++;
+      $nnrpd_timeout{$cust}++;
+      return 1;
+    }
+    # timeout in post
+    if ($left =~ /(\S+) timeout in post$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_timeout{$dom}++;
+      $nnrpd_timeout{$cust}++;
+      return 1;
+    }
+    # cant read Connection timed out
+    if ($left =~ /(\S+) cant read Connection timed out$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_timeout{$dom}++;
+      $nnrpd_timeout{$cust}++;
+      return 1;
+    }
+    # cant read Operation timed out
+    if ($left =~ /(\S+) cant read Operation timed out$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_timeout{$dom}++;
+      $nnrpd_timeout{$cust}++;
+      return 1;
+    }
+    # cant read Connection reset by peer
+    if ($left =~ /(\S+) cant read Connection reset by peer$/o) {
+      my $cust = $1;
+      $cust = lc $cust unless $CASE_SENSITIVE;
+      my $dom = &host2dom($cust);
+      $nnrpd_dom_reset_peer{$dom}++;
+      $nnrpd_reset_peer{$cust}++;
+      return 1;
+    }
+    # gethostbyaddr: xxx.yyy.zzz != a.b.c.d
+    if ($left =~ /^gethostbyaddr: (.*)$/o) {
+      my $msg = $1;
+      $nnrpd_gethostbyaddr{$msg}++;
+      return 1;
+    }
+    # cant gethostbyaddr
+    if ($left =~ /\? cant gethostbyaddr (\S+) .*$/o) {
+      my $ip = $1;
+      $nnrpd_gethostbyaddr{$ip}++;
+      return 1;
+    }
+    # cant getpeername
+    if ($left =~ /\? cant getpeername/o) {
+      # $nnrpd_getpeername++;
+      $nnrpd_gethostbyaddr{"? (can't getpeername)"}++;
+      return 1;
+    }
+    # profile timer
+    # ME time X nnnn X(X) [...]
+    # The exact timers change from various versions of INN, so try to deal
+    # with this in a general fashion.
+    if ($left =~ m/^\S+\s+                         # ME
+	           time\ (\d+)\s+                  # time
+                   ((?:\S+\ \d+\(\d+\)\s*)+)       # timer values
+                   $/ox) {
+      $nnrpd_time_times += $1;
+      my $timers = $2;
+
+      while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
+        my $name = $nnrpd_timer_names{$1} || $1;
+        my $average = $2 / ($3 || 1);
+        $nnrpd_time_time{$name} += $2;
+        $nnrpd_time_num{$name} += $3;
+        $nnrpd_time_min{$name} = $average
+          if ($3 && $nnrpd_time_min{$name} > $average);
+        $nnrpd_time_max{$name} = $average
+          if ($3 && $nnrpd_time_max{$name} < $average);
+      }
+      return 1;
+    }
+    # ME dropping articles into ...
+    return 1 if $left = ~/ME dropping articles into /o;
+    # newnews (interesting but ignored till now)
+    return 1 if $left =~ /^\S+ newnews /o;
+    # cant fopen (ignored too)
+    return 1 if $left =~ /^\S+ cant fopen /o;
+    # cant read No route to host
+    return 1 if $left =~ /cant read No route to host/o;
+    # cant read Broken pipe
+    return 1 if $left =~ /cant read Broken pipe/o;
+    # ioctl: ...
+    return 1 if $left =~ /^ioctl: /o;
+  }
+  ########
+  ## inndstart
+  if ($prog eq "inndstart") {
+    # cant bind Address already in use
+    # cant bind Permission denied
+    return 1 if $left =~ /cant bind /o;
+    # cant setgroups Operation not permitted
+    return 1 if $left =~ /cant setgroups /o;
+  }
+  ########
+  ## overchan
+  if ($prog eq "overchan") {
+    # times
+    if ($left =~ /timings (\d+) arts (\d+) of (\d+) ms$/o) {
+      my ($articles, $work_time, $run_time) = ($1, $2, $3);
+      # ??? What to do with numbers
+      return 1;
+    }
+  }
+  ########
+  ## batcher
+  if ($prog eq "batcher") {
+    # times
+    if ($left =~ /(\S+) times user (\S+) system (\S+) elapsed (\S+)$/o) {
+      my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
+      $server = lc $server unless $CASE_SENSITIVE;
+      # $batcher_user{$server} += $user;
+      # $batcher_system{$server} += $system;
+      $batcher_elapsed{$server} += $elapsed;
+      return 1;
+    }
+    # stats
+    if ($left =~ /(\S+) stats batches (\d+) articles (\d+) bytes (\d+)$/o) {
+      my ($server, $batches, $articles, $bytes) = ($1, $2, $3, $4);
+      $server = lc $server unless $CASE_SENSITIVE;
+      $batcher_offered{$server} += $batches;
+      $batcher_articles{$server} += $articles;
+      $batcher_bytes{$server} += $bytes;
+      return 1;
+    }
+  }
+  ########
+  ## rnews
+  if ($prog eq "rnews") {
+    # rejected connection
+    if ($left =~ /rejected connection (.*)$/o) {
+      $rnews_rejected{$1}++;
+      return 1;
+    }
+    # cant open_remote
+    if ($left =~ /(cant open_remote .*)$/o) {
+      $rnews_rejected{$1}++;
+      return 1;
+    }
+    # rejected 437 Unwanted newsgroup
+    if ($left =~ /rejected 437 Unwanted newsgroup \"(.*)\"$/o) {
+      $rnews_bogus_ng{$1}++;
+      return 1;
+    }
+    # rejected 437 Unapproved for "xx"
+    if ($left =~ /rejected 437 Unapproved for \"(.*)\"$/o) {
+      $rnews_unapproved{$1}++;
+      return 1;
+    }
+    # rejected 437 Unwanted distribution
+    if ($left =~ /rejected 437 Unwanted distribution (.*)$/o) {
+      $rnews_bogus_dist{$1}++;
+      return 1;
+    }
+    # rejected 437 Bad "Date"
+    if ($left =~ /rejected 437 Bad \"Date\" (.*)$/o) {
+      $rnews_bogus_date{$1}++;
+      return 1;
+    }
+    # rejected 437 Article posted in the future
+    if ($left =~ /rejected 437 Article posted in the future -- \"(.*)\"$/o) {
+      $rnews_bogus_date{"(future) $1"}++;
+      return 1;
+    }
+    # rejected 437 Too old -- "..."
+    if ($left =~ /rejected 437 Too old -- (.*)$/o) {
+      $rnews_too_old++;
+      return 1;
+    }
+    # rejected 437 Linecount...
+    if ($left =~ /rejected 437 (Linecount) \d+ \!= \d+/o) {
+      $rnews_linecount++;
+      return 1;
+    }
+    # rejected 437 Duplicate
+    if ($left =~ /rejected 437 Duplicate$/o) {
+      $rnews_duplicate++;
+      return 1;
+    }
+    # rejected 437 Duplicate article
+    if ($left =~ /rejected 437 (Duplicate article)/o) {
+      $rnews_duplicate++;
+      return 1;
+    }
+    # rejected 437 No colon-space ...
+    if ($left =~ /rejected 437 No colon-space in \"(.*)\" header$/o) {
+      $rnews_no_colon_space++;
+      return 1;
+    }
+    # duplicate <msg-id> path..
+    if ($left =~ /^duplicate /o) {
+      $rnews_duplicate++;
+      return 1;
+    }
+    # offered <msg-id> feed
+    if ($left =~ /^offered \S+ (\S+)/o) {
+      my $host = $1;
+      $host = lc $host unless $CASE_SENSITIVE;
+      # Small hack used to join article spooled when innd is throttle.
+      # In this situation, the hostname is a 8 hex digits string
+      # To avoid confusions with real feeds, the first character is forced
+      # to be a '3' or a '4' (will work between 9/7/1995 and 13/7/2012).
+      $host = "Local postings" if $host =~ /^[34][0-9a-f]{7}$/;
+      $rnews_host{$host}++;
+      return 1;
+    }
+    # rejected 437 ECP rejected
+    return 1 if $left =~ m/rejected 437 ECP rejected/o;
+    # rejected 437 "Subject" header too long
+    return 1 if $left =~ m/header too long/o;
+    # bad_article missing Message-ID
+    return 1 if $left =~ m/bad_article missing Message-ID/o;
+    # cant unspool saving to xxx
+    return 1 if $left =~ m/cant unspool saving to/o;
+  }
+
+  ###########
+  ## ncmspool
+  if ($prog eq "ncmspool") {
+    # <article> good signature from foo@bar.com
+    if ($left =~ /good signature from (.*)/o) {
+      $nocem_goodsigs{$1}++;
+      $nocem_totalgood++;
+      $nocem_lastid = $1;
+      return 1;
+    }
+    # <article> bad signature from foo@bar.com
+    if ($left =~ /bad signature from (.*)/o) {
+      $nocem_badsigs{$1}++;
+      $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
+      $nocem_totalbad++;
+      $nocem_lastid = $1;
+      return 1;
+    }
+    # <article> contained 123 new 456 total ids
+    if ($left =~ /contained (\d+) new (\d+) total ids/o) {
+      $nocem_newids += $1;
+      $nocem_newids{$nocem_lastid} += $1;
+      $nocem_totalids += $2;
+      $nocem_totalids{$nocem_lastid} += $2;
+      return 1;
+    }
+    return 1;
+  }
+
+  ###########
+  ## controlchan
+  if ($prog eq "controlchan") {
+    # loaded /x/y/z/foo.pl
+    return 1 if $left =~ m/^loaded /;
+    # starting
+    return 1 if $left =~ m/^starting/;
+    # skipping rmgroup x@y (pgpverify failed) in <foo@bar>
+    if ($left =~ m/^skipping \S+ (\S+) \(pgpverify failed\) in /) {
+      $controlchan_skippgp{$1}++;
+      $controlchan_who{$1}++;
+      return 1;
+    }
+    if ($left =~ m/^control_(sendme|ihave), [^,]+, (\S+), doit,/o) {
+      if ($1 eq "sendme") {
+	$controlchan_sendme_site{$2}++;
+      } else {
+	$controlchan_ihave_site{$2}++;
+      }
+      return 1;
+    }
+    # control_XXgroup, foo.bar [moderated] who who /x/y/12, peer, action, 1
+    #
+    # Various other random junk can end up in the moderated field, like y,
+    # unmoderated, m, etc. depending on what the control message says.  It
+    # can even have multiple words, which we still don't handle.
+    if ($left =~ m/^control_(\S+),    # type of msg
+		  \s(?:\S+)?          # newsgroup name
+		  (\s\S+)?            # optional
+		  \s(\S+)             # email
+                  \s\S+               # email
+                  \s\S+,              # filename
+                  \s\S+,              # server
+                  \s([^=,]+(?:=\S+)?),            # action
+                  \s*(.*)             # code
+                  /x) {
+      if ($1 eq 'newgroup') {
+	$controlchan_new{$3}++;
+      } elsif ($1 eq 'rmgroup') {
+	$controlchan_rm{$3}++;
+      } else {
+	$controlchan_other{$3}++;
+      }
+      $controlchan_who{$3}++;
+      $controlchan_ok{$3} += $5;
+      my $action = $4;
+      my $email = $3;
+      $action =~ s/=.*//;
+      $controlchan_doit{$email}++ if $action eq 'doit';
+      return 1;
+    }
+  }
+
+  ###########
+  ## crosspost
+  if ($prog eq "crosspost") {
+    # seconds 1001 links 3182 0 symlinks 0 0 mkdirs 0 0
+    # missing 13 toolong 0 other 0
+    if ($left =~ /^seconds\ (\d+)
+	           \ links\ (\d+)\ (\d+)
+	           \ symlinks\ (\d+)\ (\d+)
+	           \ mkdirs\ (\d+)\ (\d+)
+	           \ missing\ (\d+)
+	           \ toolong\ (\d+)
+	           \ other\ (\d+)
+	         $/ox) {
+      $crosspost_time += $1;
+      $crosspost{'Links made'} += $2;
+      $crosspost{'Links failed'} += $3;
+      $crosspost{'Symlinks made'} += $4;
+      $crosspost{'Symlinks failed'} += $5;
+      $crosspost{'Mkdirs made'} += $6;
+      $crosspost{'Mkdirs failed'} += $7;
+      $crosspost{'Files missing'} += $8;
+      $crosspost{'Paths too long'} += $9;
+      $crosspost{'Others'} += $10;
+      return 1;
+    }
+  }
+
+  ###########
+  ## cnfsstat
+  if ($prog eq "cnfsstat") {
+    # Class ALT for groups matching "alt.*" article size min/max: 0/1048576
+    # Buffer T3, len: 1953  Mbytes, used: 483.75 Mbytes (24.8%)   0 cycles
+    if ($left =~ m|^Class\ (\S+)\ for\ groups\ matching\ \S+
+                    (\ article\ size\ min/max:\ \d+/\d+)?
+                    \ Buffer\ (\S+),
+                    \ len:\ (\d+)\s+Mbytes,
+                    \ used:\ ([\d.]+)\ Mbytes\ \(\s*[\d.]+%\)
+                    \s+(\d+)\ cycles\s*
+                 $|ox) {
+      my ($class, $buffer, $size, $used, $cycles) = ($1, $3, $4, $5, $6);
+      my ($h, $m, $s) = $hour =~ m/^(\d+):(\d+):(\d+)$/;
+      my $time = $h * 3600 + $m * 60 + $s;
+      $size *= 1024 * 1024;
+      $used *= 1024 * 1024;
+      $cnfsstat{$buffer} = $class;
+
+      # If the size changed, invalidate all of our running fill rate stats.
+      if ($size != $cnfsstat_size{$buffer}) {
+        delete $cnfsstat_rate{$buffer};
+        delete $cnfsstat_samples{$buffer};
+        delete $cnfsstat_time{$buffer};
+        $cnfsstat_size{$buffer} = $size;
+      }
+      elsif ($cnfsstat_time{$buffer}) {
+        # We want to gather the rate at which cycbuffs fill.  Store a
+        # running total of bytes/second and a total number of samples.
+        # Ideally we'd want a weighted average of those samples by the
+        # length of the sample period, but we'll ignore that and assume
+        # cnfsstat runs at a roughly consistent interval.
+        my ($period, $added);
+        $period = $time - $cnfsstat_time{$buffer};
+        $period = 86400 - $cnfsstat_time{$buffer} + $time if $period <= 0;
+        $added = $used - $cnfsstat_used{$buffer};
+        if ($cycles > $cnfsstat_cycles{$buffer}) {
+          $added += $size * ($cycles - $cnfsstat_cycles{$buffer});
+        }
+        if ($added > 0) {
+          $cnfsstat_rate{$buffer} += $added / $period;
+          $cnfsstat_samples{$buffer}++;
+        }
+      }
+      $cnfsstat_used{$buffer} = $used;
+      $cnfsstat_cycles{$buffer} = $cycles;
+      $cnfsstat_time{$buffer} = $time;
+      return 1;
+    }
+  }
+
+  # Ignore following programs :
+  return 1 if ($prog eq "uxfxn");
+  return 1 if ($prog eq "beverage");
+  return 1 if ($prog eq "newsx");
+  return 1 if ($prog eq "demmf");
+  return 1 if ($prog eq "nnnn");
+  return 1 if ($prog eq "slurp");
+  return 0;
+}
+
+#################################
+# Adjust some values..
+
+sub adjust {
+  my ($first_date, $last_date) = @_;
+
+  my $nnrpd_doit = 0;
+  my $curious;
+
+  {
+    my $serv;
+    if (%nnrpd_connect) {
+      my $c = keys (%nnrpd_connect);
+      foreach $serv (keys (%nnrpd_connect)) {
+	my $dom = &host2dom($serv);
+	if ($nnrpd_no_permission{$serv}) {
+	  $nnrpd_dom_connect{$dom} -= $nnrpd_connect{$serv}
+	    if defined $nnrpd_dom_connect{$dom};
+	  $nnrpd_dom_groups{$dom}  -= $nnrpd_groups{$serv}
+	    if defined $nnrpd_dom_groups{$dom};
+	  $nnrpd_dom_times{$dom}   -= $nnrpd_times{$serv}
+	    if defined $nnrpd_dom_times{$dom};
+	  $nnrpd_connect{$serv} -= $nnrpd_no_permission{$serv};
+	  $nnrpd_groups{$serv} -= $nnrpd_no_permission{$serv}
+	    if defined $nnrpd_groups{$serv};
+	  delete $nnrpd_connect{$serv} unless $nnrpd_connect{$serv};
+	  delete $nnrpd_groups{$serv}  unless $nnrpd_groups{$serv};
+	  delete $nnrpd_times{$serv}   unless $nnrpd_times{$serv};
+	  delete $nnrpd_usr_times{$serv}   unless $nnrpd_usr_times{$serv};
+	  delete $nnrpd_sys_times{$serv}   unless $nnrpd_sys_times{$serv};
+	  delete $nnrpd_dom_connect{$dom} unless $nnrpd_dom_connect{$dom};
+	  delete $nnrpd_dom_groups{$dom}  unless $nnrpd_dom_groups{$dom};
+	  delete $nnrpd_dom_times{$dom}   unless $nnrpd_dom_times{$dom};
+	  $c--;
+	}
+	$nnrpd_doit++
+	  if $nnrpd_groups{$serv} || $nnrpd_post_ok{$serv};
+      }
+      undef %nnrpd_connect unless $c;
+    }
+    foreach $serv (keys (%nnrpd_groups)) {
+      $curious = "ok" unless $nnrpd_groups{$serv} || $nnrpd_post_ok{$serv} ||
+	$nnrpd_articles{$serv};
+    }
+  }
+
+  # Fill some hashes
+  {
+    my $key;
+    foreach $key (keys (%innd_connect)) {
+      $innd_offered{$key} = ($innd_accepted{$key} || 0)
+	+ ($innd_refused{$key} || 0)
+	+ ($innd_rejected{$key} || 0);
+      $innd_offered_size{$key} = ($innd_stored_size{$key} || 0)
+	+ ($innd_duplicated_size{$key} || 0);
+    }
+
+
+    # adjust min/max of innd timer stats.
+    if (%innd_time_min) {
+      foreach $key (keys (%innd_time_min)) {
+	$innd_time_min{$key} = 0 if ($innd_time_min{$key} == $MIN);
+	$innd_time_max{$key} = 0 if ($innd_time_max{$key} == $MAX);
+
+	#$innd_time_min{$key} /= 1000;
+	#$innd_time_max{$key} /= 1000;
+      }
+    }
+    if (%innfeed_time_min) {
+      foreach $key (keys (%innfeed_time_min)) {
+        $innfeed_time_min{$key} = 0 if ($innfeed_time_min{$key} == $MIN);
+        $innfeed_time_max{$key} = 0 if ($innfeed_time_max{$key} == $MAX);
+      }
+    }
+    if (%nnrpd_time_min) {
+      foreach $key (keys (%nnrpd_time_min)) {
+        $nnrpd_time_min{$key} = 0 if ($nnrpd_time_min{$key} == $MIN);
+        $nnrpd_time_max{$key} = 0 if ($nnrpd_time_max{$key} == $MAX);
+      }
+    }
+    # remove the innd timer stats if not used.
+    unless ($innd_time_times) {
+      undef %innd_time_min;
+      undef %innd_time_max;
+      undef %innd_time_num;
+      undef %innd_time_time;
+    }
+    # same thing for innfeed timer
+    unless ($innfeed_time_times) {
+      undef %innfeed_time_min;
+      undef %innfeed_time_max;
+      undef %innfeed_time_num;
+      undef %innfeed_time_time;
+    }
+    # same thing for nnrpd timer
+    unless ($nnrpd_time_times) {
+      undef %nnrpd_time_min;
+      undef %nnrpd_time_max;
+      undef %nnrpd_time_num;
+      undef %nnrpd_time_time;
+    }
+
+    # adjust the crosspost stats.
+    if (%crosspost) {
+      foreach $key (keys (%crosspost)) {
+	$crosspost_times{$key} = $crosspost_time ?
+	  sprintf "%.2f", $crosspost{$key} / $crosspost_time * 60 : "?";
+      }
+    }
+  }
+
+  if (%inn_flow) {
+    my ($prev_dd, $prev_d, $prev_h) = ("", -1, -1);
+    my $day;
+    foreach $day (sort datecmp keys (%inn_flow)) {
+      my ($r, $h) = $day =~ /^(.*) (\d+)$/;
+      my $d = index ("JanFebMarAprMayJunJulAugSepOctNovDec",
+		     substr ($r,0,3)) / 3 * 31 + substr ($r, 4, 2);
+      $prev_h = $h if ($prev_h == -1);
+      if ($prev_d == -1) {
+	$prev_d = $d;
+	$prev_dd = $r;
+      }
+      if ($r eq $prev_dd) { # Same day and same month ?
+	if ($h != $prev_h) {
+	  if ($h == $prev_h + 1) {
+	    $prev_h++;
+	  }
+	  else {
+	    my $j;
+	    for ($j = $prev_h + 1; $j < $h; $j++) {
+	      my $t = sprintf "%02d", $j;
+	      $inn_flow{"$r $t"} = 0;
+	    }
+	    $prev_h = $h;
+	  }
+	}
+      }
+      else {
+	my $j;
+	# then end of the first day...
+	for ($j = ($prev_h == 23) ? 24 : $prev_h + 1; $j < 24; $j++) {
+	  my $t = sprintf "%02d", $j;
+	  $inn_flow{"$prev_dd $t"} = 0;
+	}
+
+	# all the days between (if any)
+	# well, we can forget them as it is supposed to be a tool
+	# launched daily.
+
+	# the beginning of the last day..
+	for ($j = 0; $j < $h; $j++) {
+	  my $t = sprintf "%02d", $j;
+	  $inn_flow{"$r $t"} = 0;
+	}
+	$prev_dd = $r;
+	$prev_d = $d;
+	$prev_h = $h;
+      }
+    }
+    my $first = 1;
+    my (%hash, %hash_time, %hash_size, $date, $delay);
+    foreach $day (sort datecmp keys (%inn_flow)) {
+      my ($r, $h) = $day =~ /^(.*) (\d+)$/o;
+      if ($first) {
+	$first = 0;
+	my ($t) = $first_date =~ m/:(\d\d:\d\d)$/o;
+	$date = "$day:$t - $h:59:59";
+	$t =~ m/(\d\d):(\d\d)/o;
+	$delay = 3600 - $1 * 60 - $2;
+      }
+      else {
+	$date = "$day:00:00 - $h:59:59";
+	$delay = 3600;
+      }
+      $hash{$date} = $inn_flow{$day};
+      $hash_size{$date} = $inn_flow_size{$day};
+      $inn_flow_labels{$date} = $h;
+      $hash_time{$date} = $delay;
+    }
+    my ($h, $t) = $last_date =~ m/ (\d+):(\d\d:\d\d)$/o;
+    my ($h2) = $date =~ m/ (\d+):\d\d:\d\d /o;
+    my $date2 = $date;
+    $date2 =~ s/$h2:59:59$/$h:$t/;
+    $hash{$date2} = $hash{$date};
+    delete $hash{"$date"};
+    $hash_size{$date2} = $hash_size{$date};
+    delete $hash_size{"$date"};
+    $t =~ m/(\d\d):(\d\d)/o;
+    $hash_time{$date2} = $hash_time{$date} - ($h2 == $h) * 3600 + $1 * 60 + $2;
+    delete $hash_time{"$date"};
+    $inn_flow_labels{$date2} = $h;
+    %inn_flow = %hash;
+    %inn_flow_time = %hash_time;
+    %inn_flow_size = %hash_size;
+  }
+
+  if (%innd_bad_ihave) {
+    my $key;
+    my $msg = 'Bad ihave control messages received';
+    foreach $key (keys %innd_bad_ihave) {
+      $innd_misc_stat{$msg}{$key} = $innd_bad_ihave{$key};
+    }
+  }
+  if (%innd_bad_msgid) {
+    my $key;
+    my $msg = 'Bad Message-ID\'s offered';
+    foreach $key (keys %innd_bad_msgid) {
+      $innd_misc_stat{$msg}{$key} = $innd_bad_msgid{$key};
+    }
+  }
+  if (%innd_bad_sendme) {
+    my $key;
+    my $msg = 'Ignored sendme control messages received';
+    foreach $key (keys %innd_bad_sendme) {
+      $innd_misc_stat{$msg}{$key} = $innd_bad_sendme{$key};
+    }
+  }
+  if (%innd_bad_command) {
+    my $key;
+    my $msg = 'Bad command received';
+    foreach $key (keys %innd_bad_command) {
+      $innd_misc_stat{$msg}{$key} = $innd_bad_command{$key};
+    }
+  }
+  if (%innd_bad_newsgroup) {
+    my $key;
+    my $msg = 'Bad newsgroups received';
+    foreach $key (keys %innd_bad_newsgroup) {
+      $innd_misc_stat{$msg}{$key} = $innd_bad_newsgroup{$key};
+    }
+  }
+  if (%innd_posted_future) {
+    my $key;
+    my $msg = 'Article posted in the future';
+    foreach $key (keys %innd_posted_future) {
+      $innd_misc_stat{$msg}{$key} = $innd_posted_future{$key};
+    }
+  }
+  if (%innd_no_colon_space) {
+    my $key;
+    my $msg = 'No colon-space in header';
+    foreach $key (keys %innd_no_colon_space) {
+      $innd_misc_stat{$msg}{$key} = $innd_no_colon_space{$key};
+    }
+  }
+  if (%innd_huge) {
+    my $key;
+    my $msg = 'Huge articles';
+    foreach $key (keys %innd_huge) {
+      $innd_misc_stat{$msg}{$key} = $innd_huge{$key};
+    }
+  }
+  if (%innd_blocked) {
+    my $key;
+    my $msg = 'Blocked server feeds';
+    foreach $key (keys %innd_blocked) {
+      $innd_misc_stat{$msg}{$key} = $innd_blocked{$key};
+    }
+  }
+  if (%innd_strange_strings) {
+    my $key;
+    my $msg = 'Including strange strings';
+    foreach $key (keys %innd_strange_strings) {
+      $innd_misc_stat{$msg}{$key} = $innd_strange_strings{$key};
+    }
+  }
+  if (%rnews_bogus_ng) {
+    my $key;
+    my $msg = 'Unwanted newsgroups';
+    foreach $key (keys %rnews_bogus_ng) {
+      $rnews_misc{$msg}{$key} = $rnews_bogus_ng{$key};
+    }
+  }
+  if (%rnews_bogus_dist) {
+    my $key;
+    my $msg = 'Unwanted distributions';
+    foreach $key (keys %rnews_bogus_dist) {
+      $rnews_misc{$msg}{$key} = $rnews_bogus_dist{$key};
+    }
+  }
+  if (%rnews_unapproved) {
+    my $key;
+    my $msg = 'Articles unapproved';
+    foreach $key (keys %rnews_unapproved) {
+      $rnews_misc{$msg}{$key} = $rnews_unapproved{$key};
+    }
+  }
+  if (%rnews_bogus_date) {
+    my $key;
+    my $msg = 'Bad Date';
+    foreach $key (keys %rnews_bogus_date) {
+      $rnews_misc{$msg}{$key} = $rnews_bogus_date{$key};
+    }
+  }
+
+  $rnews_misc{'Too old'}{'--'} = $rnews_too_old if $rnews_too_old;
+  $rnews_misc{'Bad linecount'}{'--'} = $rnews_linecount if $rnews_linecount;
+  $rnews_misc{'Duplicate articles'}{'--'} = $rnews_duplicate
+    if $rnews_duplicate;
+  $rnews_misc{'No colon-space'}{'--'} = $rnews_no_colon_space
+    if $rnews_no_colon_space;
+
+  if (%nnrpd_groups) {
+    my $key;
+    foreach $key (keys (%nnrpd_connect)) {
+      unless ($nnrpd_groups{"$key"} || $nnrpd_post_ok{"$key"} ||
+	      $nnrpd_articles{"$key"}) {
+	$nnrpd_curious{$key} = $nnrpd_connect{$key};
+	undef $nnrpd_connect{$key};
+      }
+    }
+  }
+}
+
+sub report_unwanted_ng {
+  my $file = shift;
+  open (FILE, "$file") && do {
+    while (<FILE>) {
+      my ($c, $n) = $_ =~ m/^\s*(\d+)\s+(.*)$/;
+      next unless defined $n;
+      $n =~ s/^newsgroup //o; # for pre 1.8 logs
+      $inn_uw_ng{$n} += $c;
+    }
+    close (FILE);
+  };
+
+  unlink ("${file}.old");
+  rename ($file, "${file}.old");
+
+  open (FILE, "> $file") && do {
+    my $g;
+    foreach $g (sort {$inn_uw_ng{$b} <=> $inn_uw_ng{$a}} (keys (%inn_uw_ng))) {
+      printf FILE "%d %s\n", $inn_uw_ng{$g}, $g;
+    }
+    close (FILE);
+    chmod(0660, "$file");
+  };
+  unlink ("${file}.old");
+}
+
+###########################################################################
+
+# Compare 2 dates (+hour)
+sub datecmp {
+  # ex: "May 12 06"   for May 12, 6:00am
+  # The 2 dates are near. The range is less than a few days that's why we
+  # can cheat to determine the order. It is only important if one date
+  # is in January and the other in December.
+
+  my($date1) = substr($a, 4, 2) * 24;
+  my($date2) = substr($b, 4, 2) * 24;
+  $date1 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($a,0,3)) * 288;
+  $date2 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($b,0,3)) * 288;
+  if ($date1 - $date2 > 300 * 24) {
+    $date2 += 288 * 3 * 12;
+  }
+  elsif ($date2 - $date1 > 300 * 24) {
+    $date1 += 288 * 3 * 12;
+  }
+  $date1 += substr($a, 7, 2);
+  $date2 += substr($b, 7, 2);
+  $date1 - $date2;
+}
+
+sub host2dom {
+  my $host = shift;
+
+  $host =~ m/^[^\.]+(.*)/;
+  $host =~ m/^[\d\.]+$/ ? "unresolved" : $1 ? "*$1" : "?";
+}
+
+1;
