Initial commit. master v1.2.01k
authorThomas Hochstein <thh@inter.net>
Fri, 15 Jan 2010 08:02:12 +0000 (09:02 +0100)
committerThomas Hochstein <thh@inter.net>
Fri, 15 Jan 2010 08:02:12 +0000 (09:02 +0100)
Signed-off-by: Thomas Hochstein <thh@inter.net>
artchk.pl [new file with mode: 0644]
changes.txt [new file with mode: 0644]
domains [new file with mode: 0644]
readme.txt [new file with mode: 0644]
sample.ini [new file with mode: 0644]
sample.rc [new file with mode: 0644]

diff --git a/artchk.pl b/artchk.pl
new file mode 100644 (file)
index 0000000..c2bd001
--- /dev/null
+++ b/artchk.pl
@@ -0,0 +1,1239 @@
+#!/usr/bin/perl
+#
+#                     Automatic Article Checker
+#     v1.6  Copyright (C) June 2, 1999  by Heinrich Schramm
+#                   mailto:heinrich@schramm.com
+#
+# converted to perl by Wilfried Klaebe <wk@orion.toppoint.de>
+#   (not really converted, more or less rewritten in perl)
+#
+# modified & enhanced
+#   by Thomas Hochstein <THochstein@gmx.de> since March/April 2000
+# (c) artchk.pl (mod.) January 06, 2001 by Thomas Hochstein
+#
+# _________ ATTENTION please! - This is still a BETA version! _________
+#
+# ------------------------------------------------------------------------------
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+# ------------------------------------------------------------------------------
+#
+# You will need the following modules from CPAN:
+#    - News::NNTPClient
+#    - MIME::QuotedPrint
+#    - MIME::Base64
+#   (- Net::DNS)
+#
+# ------------------------------------------------------------------------------
+#
+# You will have to create an artchk.pl.ini / artchk.pl.rc file.
+# Please see readme.txt for details.
+#
+# Please use this program with care and sense of responsibility!
+# Thank you.
+#
+##################################################################
+
+###-### mark for temp. changes
+#-# 1.2.01g changes new in that version
+#-# {RKELLER} Contributed by Reiner Keller <keller@dlrg.de>
+
+use News::NNTPClient;
+use MIME::QuotedPrint;
+use MIME::Base64;
+use Net::DNS;
+use File::Basename;                    #-# {RKELLER}
+
+##### Constants
+$version = 'V 1.2.01k BETA';           # Stand: 2001-10-13
+$online = 0;                           # permanent connection to the net
+$serverresponse = "# NNTP:";           # intro for debugmsg (NNTP status response)
+$debugdiagmarker = "* ->";             # intro for debugmsg (diag{...} set)
+@roles = qw/abuse noc security
+            root sysop admin newsmaster
+            postmaster hostmaster usenet news webmaster www uucp ftp/;
+$hex_nibb     = '[0-9a-fA-F]';
+$gt_hex_nibb  = '[0-9A-F]';
+$lt_hex_nibb  = '[0-9a-f]';
+$alpha_num    = '[0-9a-zA-Z]';
+$lt_alpha_num = '[0-9a-z]';
+$gt_alpha_num = '[0-9A-Z]';
+# definitions from s-o-1036
+$r_unquoted_char_a = '[#$%&\'|*+{}~\-/0123456789=?A-Z^_`a-z]';                     # correct definition (for mailaddress)
+$r_unquoted_word_a = "$r_unquoted_char_a+";                                        # correct definition (for mailaddress)
+$r_unquoted_char   = '[#$%&\'|*+{}~\-/0123456789=?A-Z^_`a-z\x80-\xFF]';            # definition including \x80-\xFF (8bit)
+$r_unquoted_word   = "$r_unquoted_char+";                                          # definition including \x80-\xFF (8bit)
+$r_quoted_char     = '[!@,;:.\[\]#$%&\'|*+{}~\-/0123456789=?A-Z^_`a-z\x80-\xFF]';  # definition including \x80-\xFF (8bit)
+$r_quoted_word     = "\"($r_quoted_char|\\s)+\"";                                  # definition including \x80-\xFF (8bit)
+$r_paren_char      = '["!@,;:.\[\]#$%&\'|*+{}~\-/0123456789=?A-Z^_`a-z\x80-\xFF]'; # definition including \x80-\xFF (8bit)
+$r_paren_phrase    = "($r_paren_char|\\s)+";                                       # definition including \x80-\xFF (8bit)
+$r_plain_word      = "$r_unquoted_word|$r_unquoted_word";                          # definition including \x80-\xFF (8bit)
+$r_plain_phrase    = "$r_plain_word(\\s+$r_plain_word)*";                          # definition including \x80-\xFF (8bit)
+$r_address         = "$r_unquoted_word_a(\\.$r_unquoted_word_a)*\@$r_unquoted_word_a(\\.$r_unquoted_word_a)*";
+
+##### Main program
+# get commandline parameters
+while (@ARGV) {
+ $f = shift;
+ if ($f =~ /^-d(.*)/) {
+  $debuglevel = $1;
+ } elsif ($f =~ /-(v+)/) {
+  $debuglevel = length($1);
+ } elsif ($f =~ /-p(.*)/) {
+  $pathtoini = $1;
+ } elsif ($f =~ /-n(.*)/) {
+  $ininame = $1;
+  if ($ininame=~/\.ini$/) {
+   $ininame=~s/(.*?)\.ini$/$1/;
+  };
+ } elsif ($f =~ /-c(.*)/) {
+  $checkpost = $1;
+ } elsif ($f =~ /^-l(.*)/) {
+  $logfile = $1;
+  if ($logfile =~/\.log$/) {
+   $logfile=~s/(.*?)\.log$/$1/;
+  };
+ } elsif ($f =~ /--log/) {
+  $logging = 1;
+ } elsif ($f =~ /--feedmode/) {  #-# 1.2.01k
+  $feedmode = 1;                 #-# 1.2.01k
+ } elsif ($f =~ /--pedantic/) {  #-# 1.2.01k
+  $pedantic = 1;                 #-# 1.2.01k
+ }
+};
+
+# set parameters to default, if necessary / exit, if disabled
+if (!defined($debuglevel)) {$debuglevel = 0};
+if (!defined($ininame)) {$ininame = basename ($0)};  #-# {RKELLER}
+exit (10) if (-e "$0.disabled");                     # exit if "artchk.pl.disabled" exists
+exit (10) if (-e "$ininame.disabled");               # exit if ".disabled" exists
+if (!defined($pathtoini)) {
+ $pathtoini = dirname ($0).'/'                       #-# {RKELLER}
+} elsif ($pathtoini !~ /.*(\/|\\)$/) {
+ $pathtoini .= '/';
+} 
+if (!defined($checkpost) or ($checkpost!~/<\S+\@\S+>/)) {$checkpost = 'no'};
+if (!defined($logfile)) {$logfile = "$ininame"};
+
+# open logfile
+if ($logging) {
+ open LOG, ">>$pathtoini$logfile.log" || die "Could not open $pathtoini$logfile.log for appending: $!";
+ if ($feedmode) {                     #-# 1.2.01k
+  select((select(LOG), $| = 1)[0]);   # set autoflush
+ };
+};
+
+# print introduction
+op(10,"\n-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-\n\n");
+op(10,"This is artchk.pl (mod.) $version started on ".scalar(gmtime)." GMT.\n");
+op(0,"Send suggestions & comments to <artchk\@akallabeth.de>.\n\n");
+
+# read .rc-file / .ini-file / domains / killfile
+&readini;
+&readrcfile;
+&readdomains;
+if (-e "$killname.kill") {
+ open KILL, "<$pathtoini$killname.kill" || die "Could not open $pathtoini$killname.kill for reading: $!";
+ while (<KILL>) {
+  chomp;
+  s/#.+//;      # drop comments
+  s/^\s+//;     # drop whitespace before
+  s/\s+$//;     # drop whitespace after
+  next unless length;
+  my ($key,$value) = split(/\s*=\s*/,$_,2);    # header = regexp
+  push @{ $kill[scalar(@kill)] },lc($key),lc($value);
+ };
+ close KILL;
+};
+
+# print configuration
+op(1,"\nDebug-Level      : $debuglevel\n");
+op(1,  "Path to files    : $pathtoini\n");
+op(1,  "Filenames        : $ininame.ini / $rcname.rc / $logfile.log\n");
+op(1,  "Trigger 'check'  : $trigger_check\n");
+op(1,  "Trigger 'ignore' : $trigger_ignore\n");
+op(1,  "Newsserver (read): $server $port\n");
+op(1,  "Newsserver (post): $postingserver $postingport\n");
+if ($checkpost eq 'no') {
+ op(1, 'Groups to check  :');
+ foreach $testgroup (@testgroups) {op(1, " $testgroup")};
+} else {
+ op(11, "Posting to check: $checkpost\n");
+};
+op(1, "\n\n");
+op(1, "---------- Starting connection procedure ----------\n");
+
+if ($feedmode) {           #-# 1.2.01k ---->
+ until(eof(STDIN)){
+  $file=<STDIN>;
+  $file=~s/(\S*).*/$1/;
+  &feed_article($file);   # will set $wholeheader, @header and $wholebody (global!)
+  &check_article;
+ }
+} else {                  #-# 1.2.01k <-----
+ # open server (for reading)
+ $readserver = &connectserver($server,$port,$s_user,$s_pass);
+
+ # open server (for posting), if specified
+ if ($postingserver ne '') { $postserver = &connectserver($postingserver,$postingport,$posts_user,$posts_pass) };
+
+ # main loop - check all postings in all groups
+ op(1,"---------- Starting checks ----------");
+
+ if ($checkpost eq 'no') {
+  foreach $testgroup (@testgroups) {
+   op(1,"\n---------- New group ----------");
+   op(1,"\nOpening group $testgroup ...");
+   # get low-/high-marks of $testgroup
+   if (!(($low, $high) = ($readserver->group($testgroup)))) {
+    op(13,"\n$serverresponse Error: " . $readserver->code . ' ' . $readserver->message . "Skipping group.\n");
+    op(0,"Error opening group $testgroup on $server.\n");
+   } else {
+    op(1," done.\n");
+    op(3,"$serverresponse".$readserver->code.' '.$readserver->message);
+    op(11,"\n$testgroup: $low ---> $high; first to be checked: $watermark{$testgroup}\n");
+    if ($watermark{$testgroup} > ($high + 2)) {
+     op(1,"! High watermark of group $testgroup is to low - resetting counter ...");
+     $watermark{$testgroup} = $high;
+    }
+    if ($watermark{$testgroup} > $low) {$low = $watermark{$testgroup}};
+    if ($watermark{$testgroup} > $high) {
+     op(1,"Nothing new to check in $testgroup ... terminating.\n");
+    } else {
+     op(1,'Starting checks, ');
+     if($auto{$testgroup}) {
+      op(1,"generating followups for any problem detected (auto-mode).\n");
+     }else{
+      op(1, "only generating followups if requested.\n");
+     }
+    }
+
+    # load posting
+    for ($doit = $low; $doit <= $high; $doit += 1) {
+     $togo=$high-$doit;
+     op(2,"Now getting: __> $doit <__  ---  $togo to go ...\n");
+     &get_article($doit);   # will set $wholeheader, @header and $wholebody (global!)
+     &check_article($testgroup,$auto{$testgroup});
+    };
+
+    # remmber last tested posting
+    $watermark{$testgroup} = $high + 1;
+
+    # rewrite .ini-file
+    op(1,"Rewriting .ini-file ... ");
+    &writeini;
+    op(1,"done.\n");
+   };
+  }
+ } else {
+  op(1,"\nNow getting: $checkpost\n");
+  &get_article($checkpost);   # will set $wholeheader and $wholebody (global!)
+  &check_article('none',2);
+ }
+
+ op(1,"\n\n---------- Termination sequence ... ----------");
+ op(1,"\nartchk.pl (mod.) $version signing off.\n");
+ # close server and files
+ $readserver->quit;
+ if ($postingserver ne '') { $postserver->quit };
+};
+
+if ($logging) {
+ op(15,"$0 $version terminated successfully on ".scalar(gmtime)." GMT.\n");
+ close LOG;
+};
+op(0,"Program terminated on ".scalar(gmtime)." GMT.\n\n");
+op(0,"Thank you for using.\n");
+exit(0);
+
+################################################################
+# Main subroutines: get article / feed article / check article
+
+sub get_article {
+# load article via NNTP
+ my($doit)=@_;
+ my(@article,$lang);
+
+ # parse posting: first get headers ...
+ if (!(@article = $readserver->head($doit))) {
+  op(13,"\n$serverresponse Error: " . $readserver->code . ' ' . $readserver->message);
+  op(0,"Error reading header from $server.\n");
+ } else {
+  op(3,"$serverresponse".$readserver->code.' '.$readserver->message);
+ };
+ # split @article and add all lines to $wholeheader
+ @header = @article; # @header is global!
+ $wholeheader = '';  # $wholeheader is global!
+ $lang = @article;
+ for ($i = 0; $i <= $lang; $i+= 1) {
+  $wholeheader .= shift(@article);
+ };
+ # ... then get body
+ if (!(@article = $readserver->body($doit))) {
+  op(13,"\n$serverresponse Error: " . $readserver->code . ' ' . $readserver->message);
+  op(0,"Error reading body from $server.\n");
+ } else {
+  op(3,"$serverresponse".$readserver->code.' '.$readserver->message);
+ };
+ $wholebody = ''; # $wholebody is global!
+ $lang = @article;
+ for ($i = 0; $i <= $lang; $i+= 1) {
+  $wholebody .= shift(@article);
+ };
+};
+
+
+sub feed_article {                   #-# 1.2.01k
+# load article from disk
+ my $file = shift;
+ open(ARTICLE,"<$file") or op(15,"I/O ERROR while opening $file: $!\n");
+ $/='';
+ $wholeheader = <ARTICLE>;           # $wholeheader is global!
+ @header = split(/\n/,$wholeheader); # @header is global!
+ undef $/;
+ $wholebody = <ARTICLE>;             # $wholebody is global!
+ close(ARTICLE);
+ $/="\n";
+};
+
+
+sub check_article {
+# check article and generate followup
+ my($testgroup,$auto)=@_;
+ # $testgroup is 'none' for forced check, '' for feeding-mode
+ # $auto is '2' for forced check, '' for feeding-mode
+ # will use global $wholeheader, @header and $wholebody (from &get_article / &feed_article)
+ # will use global %config and %domain (from &readrcfile / &readdomains)
+ my(@body);                            # parts of the posting
+ my($newsreader,$nr);                  # specials
+ my($docheck,$sigokay);                # trigger
+ my(@article,@duplicate,$frdompart,$frlocpart,$rplocpart,$rpdompart,$wrongsig);
+                                       # output
+ my($m,$f,$i,$flag,$sigstart,$query,$res,@mx,$key,$value,$testgroup_q,$postgroups);
+                                       # auxiliaries
+ my($tag,$monat,$jahr,$zeit,$wtag);    # date
+ # will use global %header,%header_decoded,$debugmsg,%diag,$diaglevel
+ undef %header;
+ undef %header_decoded;
+ undef $debugmsg;
+ undef %diag;
+ undef $diaglevel;
+
+ local *ev = sub {
+ # evaluate variables
+  my ($i) = shift;
+  ($f = $config{$i}) =~ s/(\$[a-z{}'_-]+)/$1/gee;
+  $f;
+ };
+
+ # split $wholeheader into single headers
+ while ($_=shift @header) {
+  chomp;
+  if ($_ =~ /^\s+/) {
+   $_ =~ s/^\s*(.+)\s*$/$1/;
+   $header{lc($key)} .= "\n\t$_";
+  } elsif ($_ ne "\n") {
+   ($key,$value) = split /:/,$_,2;
+   if (exists $header{lc($key)}) {
+    push @duplicate,$key;
+    $diag{'duplicate'} = 1;
+   } else {
+    ($header{lc($key)} = $value) =~ s/^\s*(.+)\s*$/$1/;
+   };
+  };
+ };;
+ # return if not a test group
+ # or if posting is bot-reply or cmsg or keywords contain $trigger_ignore ...
+ if ($auto != 2) {
+  return if $header{'newsgroups'}!~/test/i;
+  return if $header{'message-id'} =~ /checkbot\.fqdn\.de>[ ]*$/i;
+  return if $header{'message-id'} =~ /checkbot-checked/i;
+  return if (defined($header{'control'}));
+  return if (defined($header{'keywords'}) and $header{'keywords'}=~/$trigger_ignore/io);
+ };
+ if ($feedmode) {                                   #-# 1.2.01k
+  $auto = 3;                                        # set $auto to unusual value
+  foreach $testgroup (@testgroups) {
+   $testgroup_q  = quotemeta($testgroup);           # quote meta characters ('.'!)
+   if ($header{'newsgroups'} =~ /$testgroup_q/) {   # if one of the test groups is found in Newsgroups: ...
+    if ($postgroups != '') {
+     $postgroups .= ',';
+    };
+    $postgroups .= $testgroup;                      # ... add it to $postgroups and ...
+    if ($auto{$testgroup} <= $auto) {
+     $auto = $auto{$testgroup};                     # ... reset $auto to the lowest value of all testgroups
+    };
+   };
+  };
+  return if $auto == 3;                             # return if $auto was not reset
+  $testgroup = $postgroups;                         # set $testgroup for posting a followup
+ };
+
+ $debugmsg .= "  --- Posting Check Results  ---\n";
+ # ... or if killfile is triggered (if check is not forced) ...
+ if ($auto != 2) {
+  $debugmsg .= "  Checking posting; it's in a test group and neither bot-reply nor cmsg.\n";
+  # check if killfile is triggered and set $flag
+  foreach (@kill) {
+   ($key,$value) = @{$_};
+   if (defined($header{$key}) and $header{$key}=~/$value/i) {
+    $flag = 1 ;
+    $debugmsg .= "  Killfile rule '$key=$value' triggered.\n";
+   };
+  }
+ };
+
+ # ... or if neither $trigger_check in Subject: nor auto-mode activated
+ $debugmsg .= "  Subject: " . $header{'subject'} . "\n";
+ if ($header{'subject'} ne &hdecode($header{'subject'})) {
+  $debugmsg .= "  Subject (decoded): " . &hdecode($header{'subject'}) . "\n";
+ };
+ if (&hdecode($header{'subject'}) =~ /$trigger_check/io or ($auto==2)) {
+  $docheck = 1;
+  if ($auto==2) {
+   $debugmsg .= "  TRIGGER: Check forced via '-c'.\n";
+   $testgroup = $header{'newsgroups'};
+  } else {
+   $debugmsg .= "  TRIGGER: Found \"$trigger_check\" in the \"Subject:\"-line, continuing check.\n";
+  };
+ }
+ else {
+  $debugmsg .= "  TRIGGER: \"$trigger_check\" not found in \"Subject:\"-line";
+  if (!$auto or (&hdecode($header{'subject'}) =~ /$trigger_ignore/io) or $flag) {
+   $debugmsg .= ", terminating check.\n";
+   $debugmsg .= "  --- End of Check Results  ---\n\n";
+   op(15,"$header{'message-id'}:\n$debugmsg\n");
+   return;
+  } else {
+   $debugmsg .= "; auto-mode activated - no ignore, continuing check.\n";
+  };
+ };
+
+ # put decoded (q/p and base64) headers in %header_decoded
+ foreach $key (keys %header) {
+  $header_decoded{$key} = &hdecode($header{$key});
+ };
+
+ # generate debugmsg for duplicate headers
+ foreach (@duplicate) {
+  $debugmsg .= "  $debugdiagmarker Duplicate header line: $_\n"
+ }
+
+ # try to detect the newsreader
+ if (defined($header{'user-agent'})) {
+  $newsreader=$header_decoded{'user-agent'}
+ } elsif(defined($header{'x-newsreader'})) {
+  $newsreader=$header_decoded{'x-newsreader'}
+ } elsif (defined($header{'x-mailer'})) {
+  $newsreader=$header_decoded{'x-mailer'}
+ }
+ if ((defined($newsreader)) and ($newsreader ne '')) {
+  KNOWN: {
+   $nr= 'oe', last KNOWN if $newsreader=~/Outlook Express/i;
+   $nr= 'moz', last KNOWN if ($newsreader=~/Mozilla/i and $newsreader!~/StarOffice/i);
+   $nr= 'agent', last KNOWN if ($newsreader=~/Forte.*Agent/i or $header{'message-id'}=~/^[a-zA-Z0-9=+]{28,34}\@/ or $header{'message-id'}=~/^$lt_alpha_num{8}\.\d{7,9}\@/ or $header{'message-id'}=~/^$lt_alpha_num{7}\.\d{2,3}\.\d\@/);
+   $nr= 'xnews', last KNOWN if ($newsreader=~/Xnews/ or $header{'message-id'}=~/^$lt_alpha_num{6}\.$lt_alpha_num{2}\.\d\@/);  #-# 1.2.01l
+   $nr= 'gnus', last KNOWN if ($newsreader=~/Gnus/i or $header{'message-id'}=~/^$lt_alpha_num{10,11}\.fsf\@/o);
+   $nr= 'slrn', last KNOWN if ($newsreader=~/slrn/i or $header{'message-id'}=~/^slrn$lt_alpha_num{6}\.$lt_alpha_num{2,3}\.\w+\@/);
+   $nr= 'macsoup', last KNOWN if ($newsreader=~/MacSOUP/i or $header{'message-id'}=~/^$lt_alpha_num{7}\.$lt_alpha_num{13,14}[A-Z]\%[a-zA-Z\.]+\@/);
+   $nr= 'mpg', last KNOWN if ($newsreader=~/Gravity/i or $header{'message-id'}=~/^MPG\.$lt_hex_nibb{22}\@/o);
+   $nr= 'pine', last KNOWN if ($newsreader=~/Gravity/i or $header{'message-id'}=~/^Pine\.$gt_alpha_num{3}\.\d\.\d{2}\.\d{14}\.\d{4,5}-\d{6}\@/o);
+   $nr= 'xp', last KNOWN if ($newsreader=~/Gravity/i or $header{'message-id'}=~/^[a-zA-Z0-9\$\-]{11}\@/o);
+   $nr= 'pminews', last KNOWN if ($newsreader=~/Gravity/i or $header{'message-id'}=~/^[a-z]{16,21}\.$alpha_num{7}\.pminews\@/o);
+  }
+ }
+ if (!defined($nr)) {
+  $nr = '-';
+  $debugmsg .= "  Could not identify newsreader.\n"
+ } else {
+  $debugmsg .= "  Newsreader identified: $newsreader [$nr].\n"
+ };
+
+ # * ---> check for 8bit in headers
+ if($wholeheader=~/[\x80-\xFF]/) {
+  $diag{'8bitheader'}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker 8bit-chars in header.\n";
+ };
+
+ # * ---> check from-header
+ ($frlocpart,$frdompart) = checkfromrp('From');
+
+ # * ---> check reply-to
+ if (defined($header{'reply-to'})) {
+  ($rplocpart,$rpdompart) = checkfromrp('Reply-To');
+ };
+
+ # * ---> check from == replyto
+ if(defined($header{'reply-to'}) && ((getmailaddress($header_decoded{'from'}))[0] eq (getmailaddress($header_decoded{'reply-to'}))[0])) {
+  $diag{'replytofrom'}=1 ;
+  $diaglevel ||= 1;
+  $debugmsg .= "  $debugdiagmarker \"From:\" = \"Reply-To:\".\n";
+ };
+
+ # * ---> check message-id
+ ($dompart = $header{'message-id'}) =~ s/.*\@(.*)>$/$1/; # fqdn isolieren
+ $dompart = lc($dompart);
+ ($tld = $dompart) =~ s/.*\.([^.]+)$/$1/;                # TLD isolieren
+ # no FQDN, but less than one word or numbers
+ if($dompart!~/(\D\w*\.)+(\D\w*$)/) {
+  $diag{'nomid'}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker MID wrong: FQDN is just one word or just numbers.\n";
+ };
+ # invalid chars in domain
+ if($dompart =~ /[^a-z0-9\-.]/) {
+  $diag{'nomid'}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker MID wrong: FQDN contains invalid characters.\n";
+ };
+ # check for valid TLD
+ if(!defined($domain{$tld})) {
+  $diag{'nomid'}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker MID wrong: no valid TLD.\n";
+ };
+ # check for <unique%mailaddress@do.main> (USEFOR, used e.g. by MacSOUP)
+ ($f,undef,undef,$i,undef) = &getmailaddress($header_decoded{'from'});
+ $f = quotemeta($f);
+ if ($header{'message-id'} =~ /%$f>$/) {
+  $debugmsg .= "  MID is <unique%address\@do.main>, see draft-ietf-usefor-msg-id-alt-00,\n";
+  $debugmsg .= "  chapter 2.1.2 - not yet a good idea (but we do not mind ;-)).\n";
+ } elsif (($dompart eq $i) and ($nr eq 'moz') and ($header{'message-id'} =~ /^<$gt_hex_nibb{8}\.$gt_hex_nibb{4,8}\@/)) {
+  # Mozilla generates the MID from the FQDN of the mailaddress
+  $diag{'nomid'}=1;
+  $diaglevel ||= 1;
+  $debugmsg .= "  $debugdiagmarker MID wrong: Mozilla takes FQDN of mailaddress.\n";
+ } elsif($dompart=~/^gmx\.(de|net|at|ch|li)$/) {
+  # special: GMX does not offer usenet service
+  $diag{'nomid'}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker MID wrong: GMX does not offer usenet service.\n";
+ };
+ if (defined($diag{'nomid'})) {
+  $debugmsg .= "  MID was \"$header{'message-id'}\".\n";
+ };
+
+ # * ---> check date
+ ($wtag,$tag,$monat,$jahr,$zeit) = (split / +/, $header{'date'});
+ if (!($wtag=~/\w{3},/)) {
+  $zeit = $jahr;
+  $jahr = $monat;
+  $monat = $tag;
+  $tag = $wtag;
+ };
+ if ($jahr < 1970) {
+  $diag{'date'}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker \"Date:\" is incorrect: year < 1970.\n";
+  $debugmsg .= "  \"Date:\" was: \"$header{'date'}\".\n";
+ };
+
+ # * ---> check for html
+ if(defined($header{'content-type'})&&$header{'content-type'}=~/html/i) {
+  $diag{'html'}=1;
+  $diaglevel=1;
+  $debugmsg .= "  $debugdiagmarker HTML detected (no multipart/alternative!).\n";
+ };
+
+ # * ---> check for multiparts
+ if(defined($header{'content-type'}) and ($header{'content-type'}=~/multipart/)) {
+  $diag{'multipart'}=1;
+  $diaglevel ||= 1;
+  $debugmsg .= "  $debugdiagmarker MIME-multipart identified.\n";
+  $debugmsg .= "  Cannot check body of that posting - do not understand multipart yet.\n";
+ };
+
+ # apply checks to body only if there _is_ a body
+ if (defined($wholebody)) {
+  if(defined($header{'content-transfer-encoding'})) {
+  
+   # * ---> check for q/p (body)
+   if ($header{'content-transfer-encoding'}=~/quoted/i){
+    # $diag{'qp'}=1;    #-# 1.2.01k 
+    # $diaglevel ||= 1; #-# 1.2.01k
+    # $debugmsg .= "  $debugdiagmarker Content-transfer-encoding: quoted/printable.\n"; #-# 1.2.01k
+    $debugmsg .= "  Content-transfer-encoding: quoted/printable.\n";
+    $debugmsg .= "  Will decode that body now.\n";
+    # convert quoted-printables to 8bit
+    # $wholebody=~s/[ \t]\n/\n/sg;  # RFC 1521 5.1 Rule #3
+    $wholebody=~s/=\n//sg;        # RFC 1521 5.1 Rule #5
+    $wholebody=~s/=([0-9a-fA-F]{2})/pack("H2",$1)/sge;
+   };
+   
+   # * ---> check for base64 (body)
+   if($header{'content-transfer-encoding'}=~/base64/i){
+    $diag{'base64'}=1;
+    $diaglevel=1;
+    $debugmsg .= "  $debugdiagmarker Content-transfer-encoding: Base64.\n";
+    $debugmsg .= "  Will decode that body now.\n";
+    $wholebody = decode_base64($wholebody); # do Base64-decoding
+   };
+  }
+
+  # split $wholebody into single lines
+  @body=split("\n",$wholebody);
+
+  # terminate and return if $trigger_ignore is found in first line
+  # (and $trigger_check not in Subject:) --> $docheck
+  if (!$docheck and ($body[0] =~ /$trigger_ignore/io)) {
+   $debugmsg .= "  TRIGGER: \"$trigger_ignore\" found in \"Subject:\"-line or first line of posting; terminating check.\n";
+   $debugmsg .= "  --- End of Check Results  ---\n\n";
+   op(15,"$header{'message-id'}:\n$debugmsg\n");
+   return;
+  }
+
+  # * ---> check for charset / transfer-encoding
+  if(!defined($diag{'multipart'})) {
+   if($wholebody=~/[\x80-\xff]/){
+#    (@problem) = $wholebody=~/([\x80-\xff])/g;
+    $debugmsg .= "  Found 8bit-characters in body.\n";
+#    $debugmsg .= "  @problem\n";
+    if(defined($header{'content-type'})){
+     if($header{'content-type'}!~/charset=/i){
+      $diag{'nocharset'}=1;
+      $diaglevel=2;
+      $debugmsg .= "  $debugdiagmarker Header \"Content-Type:\" does not define charset.\n";
+     };
+     if($header{'content-type'}=~/us-ascii/) {
+      $diag{'nocharset'}=1;
+      $diaglevel=2;
+      $debugmsg .= "  $debugdiagmarker Charset is \"US-ASCII\".\n";
+     };
+    }else{
+     $diag{'nocharset'}=1;
+     $diaglevel=2;
+     $debugmsg .= "  $debugdiagmarker No charset defined.\n";
+    }
+    if(!defined($header{'content-transfer-encoding'})) {
+     $diag{'nocontenttransferenc'}=1;
+     $diaglevel=2;
+     $debugmsg .= "  $debugdiagmarker No content-transfer-encoding defined.\n";
+    };
+   }
+  }
+
+  # * ---> check for vcards
+  if($wholebody=~/(^begin:vcard\s*$)(.|[\n])+(^end:vcard\s*$)/im) {
+   $diag{'vcard'}=1;
+   $diaglevel ||= 1;
+   $debugmsg .= "  $debugdiagmarker V-Card identified.\n";
+  };
+
+  $sigstart = $#body;
+  # sig-delimiter and (too) long sigs
+  for($i=$#body;$i>-1;$i--){        #-# 1.2.01i: fixed sigdelimiter first line
+   if (defined($diag{'base64'})) {
+    $body[$i] =~ s/\r$//;   # remove carriage returns (CR-CR-LF to CR-LF)
+   };
+   if($body[$i]=~/^(- )?--[^-]?\s*$/){
+    $sigstart = $i;
+    $debugmsg .= "  Possible sig-delimiter found in line " . ($i+1) . " of posting.\n";
+    if ($body[$i] !~/^(- )?-- $/ and $sigokay == 0) {
+     $diag{'sigdelimiter'} = 1;
+     $wrongsig = $body[$i];
+     $debugmsg .= "  $debugdiagmarker Sig-delimiter in line " . ($i+1) . " is wrong.\n";
+     $debugmsg .= "  Sig-delimiter was \"$wrongsig\".\n";
+    } else {
+     $sigokay ||= $i+1;
+     $debugmsg .= "  Sig-delimiter is correct or not checked.";
+     if ($diag{'sigdelimiter'} == 1) {
+      delete $diag{'sigdelimiter'};
+      $debugmsg .= " - Reset error-message.\n";
+     } else {
+      $debugmsg .= "\n";     };
+    };
+    if($sigokay > 0) {
+     $f = $sigokay-1;
+    } else {
+     $f = $i;
+    };     
+    if ($f+4<$#body) {
+     $diag{'longsig'}=1;
+     $diaglevel ||= 1;
+     $debugmsg .= "  $debugdiagmarker Sig is too long [starting line ".($f+2)." - ending line ".($#body+1)."].\n";
+    };
+   }
+  }
+  if ($diag{'sigdelimiter'} == 1) {$diaglevel ||= 1;};
+
+  # lines to long
+  LINECHECK:
+  for ($i=$sigstart; $i>=0; $i--) {
+   last LINECHECK if(!defined($body[$i]));
+   if(($body[$i]=~/^.{75,}$/) and ($body[$i]!~/^[ ]*[>|:]/)) {
+    $diag{'longlines'}=1;
+    $diaglevel ||= 1;
+    $debugmsg .= "  $debugdiagmarker Line " . ($i+1) . " too long and not quoted.\n";
+    $debugmsg .= "  Offending line: " . $body[$i] . "\n";
+   };
+  }
+
+  if ($sigstart < $#body) {
+   SIGCHECK:
+   for ($i=$sigstart; $i<=$#body; $i++) {
+    last SIGCHECK if(!defined($body[$i]));
+    if($body[$i]=~/^.{81,}$/) {
+     $diag{'longlinesig'}=1;
+     $diaglevel ||= 1;
+     $debugmsg .= "  $debugdiagmarker Line " . ($i-$sigstart) . " of signature too long.\n";
+     $debugmsg .= "  Offending line: " . $body[$i] . "\n";
+    }
+   }
+  }
+ } else {
+  $debugmsg .= "  Message does not contain body.\n"
+ };
+
+ # if config for any problem is empty (.rc-file!), reset diag
+ foreach $i (keys %diag) {
+  if(!defined($config{$i}) and defined($diag{$i})) {
+   delete $diag{$i};
+   $debugmsg .= "  ! Configuration: Text for [$i] missing - problem was found, but won't be reported.\n";
+  }
+ }
+
+ # increase diaglevel if pedantic is on
+ if ($pedantic) { $diaglevel += 1; };
+
+ $debugmsg .= "  --- End of Check Results  ---\n\n";
+
+ # if subject == 'check' or auto == 1 and $diaglevel > 1:
+ # post followup
+ if ($docheck or ($auto && ($diaglevel > 1))) {
+  op(2,"Got one! ---> $header{'message-id'}\n");
+  op(2,"Generating followup, writing to $testgroup ...");
+  # generate followup
+  @article = $config{'head'};
+  push @article, "Newsgroups: $testgroup\n";
+  ($m=$header{'message-id'})=~s/\@(.*)>$/%$1/;
+  push @article, 'Message-ID: '.$m.'@checkbot.fqdn.de>'."\n";
+  if(defined($header{'references'})) {
+   push @article, "References: $header{'references'} $header{'message-id'}\n"
+  } else {
+   push @article, "References: $header{'message-id'}\n"
+  } 
+  ($wtag,$monat,$tag,$zeit,$jahr) = (split / +/, (scalar gmtime));
+  push @article, "Date: $wtag, $tag $monat $jahr $zeit GMT\n";
+  $f = "Subject: ";
+  $f .= "Re: " unless ($header_decoded{'subject'}=~/^[ \t]*re:/i);
+  $f .= &encode_header($header_decoded{'subject'});
+  push @article, "$f\n";
+  push @article, "X-Artchk-Version: artchk.pl (mod.) $version\n";
+  if($header_decoded{'subject'}=~/(^|\s)replybymail(\s|$)/) {
+   push @article, "X-Sorry: 'replybymail' not supported in this version.\n";
+  }
+  if ($auto==2) {
+   push @article, "X-Comment: Check enforced by operator using '$0 -c'.\n";
+  }
+  push @article, "MIME-Version: 1.0\n";
+  push @article, "Content-Type: text/plain; charset=ISO-8859-1\n";
+  push @article, "Content-Transfer-Encoding: 8bit\n"; 
+  push @article, "\n";
+  if ($auto==2) {
+   push @article, $config{'header-forced'}
+  } elsif ($docheck) {
+   push @article, $config{'header'}
+  } elsif ($auto) {
+   push @article, $config{'header-auto'}
+  } else {
+   push @article, "\nCHECKBOT INTERNAL ERROR!\n"
+  }
+  push @article, "\n";
+  push @article, "$header_decoded{'from'} schrieb:\n\n";
+  if(scalar @body==0){push @article, "[nichts]\n\n"}
+  else{for(0..4){push @article, '>'.$body[$_]."\n" if defined $body[$_]}}
+  push @article, "[...]\n" if (defined($body[5]));
+  push @article, "\n";
+  
+  if(scalar keys %diag !=0){
+   push @article, $config{'intro'},"\n";
+   if (defined($diag{'duplicate'}) && $diag{'duplicate'}==1) {
+    push @article, $config{'duplicate'},"\n";
+    while ($_=shift @duplicate) {
+     push @article, "|     $_\n";
+    };
+    push @article, "\n";                            #-# 1.2.01k
+   };
+   foreach $i (qw/from from-domain from-roles noname reply-to reply-to-domain reply-to-roles replytofrom date 
+                  nomid 8bitheader nocharset nocontenttransferenc sigdelimiter longsig
+                 multipart base64 html vcard longlines longlinesig /){       #-# 1.2.01k: removed qp
+    if (defined($diag{$i}) && $diag{$i}==1) {
+     push @article, ev($i), "\n";       # Variablen expandieren
+    };
+    if (defined($config{"$i-$nr"}) && $diag{$i}==1) {
+     push @article, ev("$i-$nr"), "\n"; # Variablen expandieren
+    };
+   };
+   if (defined($config{'umlauts'})) {
+    push @article, $config{'umlauts'},"\n" if((defined($diag{'nocharset'})) && ($diag{'nocharset'}==1) or
+                                              (defined($diag{'8bitheader'})) && ($diag{'8bitheader'}==1));
+   };
+   if (defined($config{'violation'}) && $diaglevel > 1) {   #-# 1.2.01k
+    push @article, "$config{'violation'}\n";
+   };
+   push @article, $config{'nr'},"\n";
+   if (defined($nr) and (defined($config{$nr}))) {
+    push @article, ev('nr-known'), "\n"; # Variablen expandieren
+    push @article, ev($nr), "\n";        # Variablen expandieren
+   };
+   if (defined($header{'x-trace'}) and ($header{'x-trace'}=~/^fu-berlin.de/) and defined($config{'newscis'})) {
+    push @article, "$config{'newscis'}\n";
+   };
+  }else{
+   push @article, $config{'allok'},"\n";
+  }
+  
+  if ($header_decoded{'subject'}=~ /$trigger_check verbose/io) {
+   push @article, $config{'debug'};
+   ($f=$debugmsg)=~s/\n/\n\| /g;
+   $f = '| ' . $f . "\n\n";
+   push @article, $f;
+  };
+  
+  push @article, $config{'footer'};
+  
+  if ($feedmode) {           #-# 1.2.01k ---->
+   # open server for posting
+   if ($postingserver ne '') {
+    $postserver = &connectserver($postingserver,$postingport,$posts_user,$posts_pass)
+   } else {
+    $postserver = &connectserver($server,$port,$s_user,$s_pass);
+   };
+   $f = \$postserver;
+  } else {                   #-# 1.2.01k <-----
+   if ($postingserver ne '') {
+    $f = \$postserver;
+    $i = "$postingserver";
+    if ($postingport ne '') {
+      $i .= "(Port $postingport)";
+    }
+   } else {
+    $f = \$readserver;
+    $i="$server";
+    if ($port ne '') {
+      $i .= " (Port $port)";
+    }
+   }
+  };
+  if (!($$f->post(@article))) {                                             #-# 1.2.01g
+   op(13,"\n$serverresponse Error: " . $$f->code . ' ' . $$f->message);
+   op(0,"Error writing followup to $i.\n");
+   if ($$f->message =~ /imeout/ and $postingserver ne '') {
+    op(10,"Retry due to timeout ...");
+    $postserver = &connectserver($postingserver,$postingport,$posts_user,$posts_pass);
+    if (!($postserver->post(@article))) {
+     op(13,"\n$serverresponse Error: " . $$f->code . ' ' . $$f->message);
+     op(0,"Error writing followup to $i during retry.\n");
+    } else {
+     op(2," done (written to $i).\n");
+     op(2,"Message-ID was $m\@checkbot.fqdn.de>.\n");
+     op(3,"$serverresponse".$postserver->code.' '.$postserver->message);
+    };
+   };
+  } else {
+   op(2," done (written to $i).\n");
+   op(2,"Message-ID was $m\@checkbot.fqdn.de>.\n");
+   op(3,"$serverresponse".$$f->code.' '.$$f->message);
+  };
+  if ($feedmode) {           #-# 1.2.01k
+   $postserver->quit;
+  };
+  op(14,"\n$header{'message-id'}:\n$debugmsg\n\n");
+ } else {
+  op(15,"$header{'message-id'}:\n$debugmsg\n");
+ }
+} 
+
+################################################################
+# Subroutines: get_mail_address
+#              encode_header / hdecode / dodecode
+#              evaluate variables
+#              generic output routinte (instead of 'print')
+#              connect to server
+
+sub getmailaddress {
+ my($raw)=shift;
+ my($tmp,$address,$name,$lp,$dp,$type);
+ if($raw=~/^<?($r_address)>?$/) {
+  $type = 1;
+  $address = $1; 
+  $name = '';
+ } elsif($raw=~/^($r_address)\s+\($r_paren_phrase\)\s*$/) {
+  $type = 2;
+  $address = $1; 
+  $tmp = quotemeta($address);
+  ($name = $raw) =~ s/^$tmp\s+\(([^()]+)\)$/$1/;
+ } elsif($raw=~/^(($r_quoted_word|$r_unquoted_word)(\s+($r_quoted_word|$r_unquoted_word))*)\s+<$r_address>\s*$/) {
+  $type = 3;
+  $name = $1;
+  ($address = $raw) =~ s/.*<($r_address)>\s*$/$1/;
+ };
+ ($lp = $address) =~ s/^([^@]+)@.*/$1/;
+ ($dp = $address) =~ s/\S*\@(\S*)$/$1/;
+ chomp ($address, $name, $lp, $dp, $type);
+ foreach $tmp ($address, $name, $lp, $dp, $type) {
+  $tmp = lc($tmp);
+ };
+ return $address, $name, $lp, $dp, $type;
+}
+
+sub checkfromrp {
+ # * ---> check from-header / reply-to
+ my ($headername) = shift;
+ my $hname = lc($headername);
+ my($address,$name,$locpart,$dompart,$type)=&getmailaddress($header_decoded{$hname});
+ my $tld;
+ ($tld = $dompart) =~ s/.*\.([^.]+)$/$1/;        # isolate TLD
+ $tld = lc($tld);
+ if ($hname eq 'from') {
+  if($type==1) {
+   $debugmsg .= "  \"From:\"-header is type 1 [address\@do.main].\n";
+  }elsif($type==2) {
+   $debugmsg .= "  \"From:\"-header is type 2 [address\@do.main (full name)].\n";
+  }elsif($type==3) {
+   $debugmsg .= "  \"From:\"-header is type 3 [full name <address\@do.main>].\n";
+  }else{
+   $diag{'from'}=1;
+   $diaglevel=2;
+   $debugmsg .= "  $debugdiagmarker \"From:\"-syntax is incorrect.\n";
+  };
+ } else {
+  if($type==0) {
+   $diag{'reply-to'}=1;
+   $diaglevel=2;
+   $debugmsg .= "  $debugdiagmarker \"Reply-To:\" is incorrect.\n";
+  };
+ }
+ $f = lc($dompart);
+ if($f =~ /[^a-z0-9\-.]/) {
+  $diag{$hname}=1;
+  $diaglevel=2;
+  $debugmsg .= "  $debugdiagmarker \"$headername:\" is incorrect: invalid chars in domain.\n";
+ };
+ if($type!=0) {
+  # domain
+  if(!defined($domain{$tld})) {
+   $diag{"$hname".'-domain'}=1;
+   $diaglevel=2;
+   $debugmsg .= "  $debugdiagmarker \"$headername:\" is incorrect: no valid TLD.\n";
+  # MX-/A-lookup
+  } else {
+   if ($online) {
+    $res = Net::DNS::Resolver -> new();
+    $res->usevc(1);
+    $res->tcp_timeout(15);
+    $i='okay';          #-# 1.2.01i: fixed 'bug' in logging DNS-checks
+    @mx = mx($res,$dompart) or $i = $res->errorstring;
+    $debugmsg .= "  DNS (\"$headername:\"): $i.\n";   #-# 1.2.01i: fixed 'bug' in logging DNS-checks
+    if ($i eq 'NXDOMAIN' or $i eq 'NOERROR') {
+     $debugmsg .= "  No MX-record for \"$dompart\": $i.\n";
+     $i='okay';      #-# 1.2.01i: fixed 'bug' in logging DNS-checks
+     $query = $res->search($dompart) or $i = $res->errorstring;
+     $debugmsg .= "  DNS (\"$headername:\"): $i.\n";   #-# 1.2.01i: fixed 'bug' in logging DNS-checks
+     if ($i eq 'NXDOMAIN' or $i eq 'NOERROR') {
+      $debugmsg .= "  $debugdiagmarker No A-record either: $i - \"$headername:\" is not replyable.\n";
+      $diag{"$hname".'-domain'}=1;
+      $diaglevel=2;
+     };
+    };
+   };
+  };
+  # no name, just address?
+  if ($hname eq 'from') {
+   if($name !~ /[a-z][^.]\S*\s+\S*([a-z][^.]\S*)+/i) {
+    $diag{'noname'}=1;
+    $diaglevel ||= 1;
+    $debugmsg .= "  $debugdiagmarker \"From:\" does not contain full name.\"\n";
+   };
+  };
+  # check for role accounts
+  ROLES: foreach $f (@roles) {
+   if ($f eq lc($locpart)) {
+    $diag{"$hname".'-roles'}=1;
+    $diaglevel ||= 1;
+    $debugmsg .= "  $debugdiagmarker \"$headername:\" contains role account.\"\n";
+    last ROLES;
+   };
+  };
+ };
+ if (defined($diag{$hname}) or defined($diag{"$hname".'-domain'}) or defined($diag{"$hname".'-roles'}) or ($debuglevel > 4)) {
+  $debugmsg .= "  \"$headername:\": \"$header{$hname}\".\n";
+  if ($header{$hname} ne $header_decoded{$hname}) {
+   $debugmsg .= "  \"$headername:\" (decoded): \"$header_decoded{$hname}\".\n";
+  };
+ } elsif (defined($diag{'noname'}) and ($hname eq 'from')) {
+  $debugmsg .= "  \"From:\": \"$header{'from'}\".\n";
+  if ($header{'from'} ne $header_decoded{'from'}) {
+   $debugmsg .= "  \"From:\" (decoded): \"$header_decoded{'from'}\".\n";
+  };
+ };
+ return ($locpart,$dompart);
+}
+
+sub encode_header {
+ my $header=shift;
+ my ($word,$space,$encoded_header);
+ while ($header=~/(\S+)(\s*)/g) {
+  ($word,$space) = ($1,$2);
+  if ($word=~/[\x80-\xFF]/) {
+   $word='=?iso-8859-1?Q?'.encode_qp($word).'?=';
+  }
+  $encoded_header .= "$word$space";
+ }
+ $encoded_header =~ s/\?=(\s+)=\?iso-8859-1\?Q\?/$1/g;
+ return $encoded_header;
+}
+
+
+sub hdecode {
+  my $header=shift;
+  if ($header=~/=\?.*\?(.)\?(.*)\?=/) {
+    $header=~s/=\?.*\?(.)\?(.*)\?=/&dodecode($1,$2)/ge;
+  };
+  $header=~s/\n\t//; # unfold headers
+  return $header;
+}
+
+
+sub dodecode {
+ # decode RFC 1522 headers
+ my $enc=shift;
+ my $etext=shift;
+
+ if($enc=~/^q$/i){
+  $etext=decode_qp($etext);
+  $etext=~s/_/' '/ge;
+ }elsif($enc=~/^b$/i){
+  $etext=decode_base64($etext); 
+ }else{$etext=''}
+ return $etext;
+}
+
+
+sub op {
+# (debug) output
+# level 0         : error messages, introduction/end
+# level 1 (-v)    : + configuration and summaries 
+# level 2 (-vv)   : + progress indicator
+# level 3 (-vvv)  : + NNTP-replies from server(s)
+# level 4 (-vvvv) : + debug-output from check-routines
+# level >=10      : output also to logfile if activated
+ my($level,$text,$handle) = @_;
+ if ($level >= 10) {
+  if ($logging) { print LOG $text; };
+  $level -= 10;
+ };
+ $handle ||= 'STDOUT';
+ if ($debuglevel >= $level and not $feedmode) {
+  print $handle $text;
+ };
+}
+
+
+
+sub connectserver {
+ my($server,$port,$s_user,$s_pass) = @_;
+
+ # connect to server
+ op(0,"Connecting to news server ...");
+ my $c = new News::NNTPClient($server,$port);
+
+ if (!($c->code)) {
+  op(0,"\nCan't connect to server. Aborting.\n");
+  die "\nCan't connect to server. Aborting.\n";
+ } else {
+  op(0," done.\n");
+ }
+
+ $c->postok() or op(0,"Server does not allow posting?!\n");
+
+ op(3,"$serverresponse".$c->code.' '.$c->message);
+
+ # switch off error messages from News::NNTPClient
+ $c->debug(0);
+
+ # mode reader
+ op(1,'MODE reader ...');
+ if (!($c->mode_reader)) {
+  op(10,"\n$serverresponse Error: " . $c->code . ' ' . $c->message . "Aborting.\n");
+  die '$serverresponse Error: ' . $c->code . ' ' . $c->message . "Aborting.\n";
+ } else {
+  op(1," done.\n");
+  op(3,"$serverresponse".$c->code.' '.$c->message);
+ };
+
+ # authorize, if needed
+ if ($s_user ne '') {
+  op(1,"Authentification ...");
+  if ($c->authinfo($s_user,$s_pass)) {
+   op(1," done.\n");
+  } else {
+   op(0,"\nAuthentification failure. Aborting.\n");
+   die "\nAuthentification failure. Aborting.\n";
+  }
+  op(3,"$serverresponse", $c->code, ' ', $c->message);
+ };
+
+ op(0,"\n");
+ $c;
+}
+
+
+################################################################
+# Subroutines for reading / writing files
+# - read rc
+# - read/write ini
+# - read domains
+
+sub readrcfile {
+ my($a,$i);
+ open(RC,'<'.$pathtoini.$rcname.'.rc')||die "Could not open $pathtoini"."$rcname.rc for reading: $!";
+ $a='';
+ until(eof(RC)){
+  $i=<RC>;
+  next if(substr($i,0,1) eq ';');
+  if($i=~/^\[.*\]$/){ $a=substr($i,1,-2);next; }
+  $config{$a}.=$i;
+ }
+ close(RC);
+ # check for _necessary_ entries
+ foreach $i (qw/head header header-auto footer intro allok nr debug/) {
+  if(!defined($config{$i})){
+   op(0,"The entry [$i] is missing in the $rcname.rc file. This entry\n");
+   op(0,"is necessary.\n");
+   exit(1);
+  }
+ }
+ # check for other entries
+ foreach $i (qw/multipart html vcard nocharset nocontenttransferenc base64 nomid
+             nomid-moz longlines longlinesig 8bitheader replytofrom reply-to sigdelimiter
+             sigdelimiter-oe date from noname longsig umlauts nr-known oe moz agent
+             xnews gnus macsoup slrn newscis from-domain from-roles reply-to-domain reply-to-roles/) {  #-# 1.2.01k: removed qp
+  if(!defined($config{$i})){
+   op(0,"\n.rc: The entry [$i] is missing in the $rcname.rc file.\n");
+   op(0,"     Corresponding check will be skipped.\n");
+  }
+ }
+}
+
+
+sub readini {
+ my($a,$b,$c);
+ open(INI,'<'.$pathtoini.$ininame.'.ini')||die "Could not open $pathtoini"."$ininame.ini for reading: $!";
+ until(eof(INI)) {
+  $c=<INI>;
+  if ($c=~/=/) {         # if '=' is found in line
+   chomp(($a,$b)=split(/=/,$c));   # split it into parametername and -contents
+   $a=~s/^\s*(.*?)\s*$/$1/g;        # delete leading/trailing whitespace
+   $b=~s/^\s*(.*?)\s*$/$1/g;        # delete leading/trailing whitespace
+   if ($a eq 'reader') {
+    chomp(($server,$port)=split(/,/,$b));
+   } elsif ($a eq 'reader_user') {
+    chomp($s_user=$b);
+   } elsif ($a eq 'reader_pass') {
+    chomp($s_pass=$b);
+   } elsif ($a eq 'poster') {
+    chomp(($postingserver,$postingport)=split(/,/,$b));   
+   } elsif ($a eq 'poster_user') {
+    chomp($posts_user=$b);
+   } elsif ($a eq 'poster_pass') {
+    chomp($posts_pass=$b);
+   } elsif ($a eq 'trigger_check') {
+    chomp($trigger_check=$b);
+   } elsif ($a eq 'trigger_ignore') {
+    chomp($trigger_ignore=$b);
+   } elsif ($a eq 'rcfile') {
+    chomp($rcname=$b);
+   } elsif ($a eq 'killfile') {
+    chomp($killname=$b);
+   }
+  } elsif ($c =~/checkgroups:/) {
+   until(eof(INI)){
+    chomp(($a,$b,$c)=split(/ /,<INI>));
+    @testgroups = (@testgroups, $a) unless ($a!~/^\w+(\.\w+)+/);
+    if ($b eq 'y') {
+     $auto{$a} = 1;
+    }else{
+     $auto{$a} = 0;
+    };
+    $watermark{$a} = $c;
+    if (!defined($watermark{$a})) {$watermark{$a} = 0};
+   }
+  }
+ }
+ close(INI);
+ if($server eq '') {
+  op(0,"You have to define a reading server in $ininame.ini\n");
+  exit(1);
+ }
+ if($trigger_check eq '') {
+  $trigger_check='check';
+ }
+ if($trigger_ignore eq '') {
+  $trigger_ignore='(ignore)|(no[ ]*repl(y|(ies)))|(nocheck)';
+ }
+ if($rcname eq '') {
+  $rcname=$ininame;
+ } elsif ($rcname=~/\.rc$/) {
+  $rcname=~s/(.*?)\.rc$/$1/;
+ }
+ if($killname eq '') {
+  $killname=$ininame;
+ } elsif ($killname=~/\.kill$/) {
+  $killname=~s/(.*?)\.kill$/$1/;
+ }
+ if(scalar(@testgroups) == 0) {
+  op(0,"You have to define at least one testgroup in $ininame.ini\n");
+  exit(1);
+ }
+}
+
+
+sub writeini {
+ my($r,$tmp,$point);
+ open(INI,'<'.$pathtoini.$ininame.'.ini')||die "Could not open $pathtoini"."$ininame.ini for reading: $!";
+ until(eof(INI)) {
+  $r = <INI>;
+  $tmp .= $r;
+  if ($r =~/checkgroups:/) {
+   last;
+  }
+ }
+ close (INI);
+ open(INI,'>'.$pathtoini.$ininame.'.ini')||die "Could not open $pathtoini"."$ininame.ini for writing: $!";
+ print INI $tmp;
+ foreach $testgroup (@testgroups) {
+  print INI "$testgroup ";
+  if ($auto{$testgroup} == 0) {
+   print INI 'n '
+  }else{
+      print INI 'y '
+  };
+  print INI "$watermark{$testgroup}\n";
+ }
+ close(INI);
+}
+
+
+sub readdomains {
+ my ($i,@domains); 
+ open(DOM,'<'.$pathtoini.'domains')||die "Could not open \"$pathtoini"."domains\" for reading: $!";
+ chomp(@domains = split(/ /,<DOM>));
+ close(DOM);
+ $i = 0;
+ until(!defined(@domains[$i])) {
+  $domain{$domains[$i]} = 'valid';
+  $i++;
+ };
+}
+
+__END__
diff --git a/changes.txt b/changes.txt
new file mode 100644 (file)
index 0000000..c53433d
--- /dev/null
@@ -0,0 +1,169 @@
+Changes:     (Version History)\r
+\r
+V 1.2\r
+V 1.2.02 (not released yet)\r
+- new: direct feeding mode (experimental) (1.2.01k)\r
+- new: "pedantic" mode; all violations will be reported in auto-mode\r
+- chg: RFC-violations marked with "*" instead of "-" in output (1.2.01k)\r
+- chg: only RFC-violations will be reported in auto-mode;\r
+       everything else is considered a minor problem (1.2.01k)\r
+- chg: "quoted/printable" no longer checked (1.2.01k)\r
+- chg: new "artchk.pl.disabled" will disable _all_ instances\r
+- chg: sample.rc corrected (new URLs)\r
+- fix: $diaglevel was not set for problems in From:/Reply-To: (1.2.01k)\r
+- fix: signature delimiter in first line of posting wasn't checked (1.2.01i)\r
+- fix: alternative postingserver could run in timeout (1.2.01g)\r
+- fix: "mode reader" must be send _before_ authentification (1.2.01g)\r
+- fix: default for $ininame contained full path (1.2.01g)\r
+- fix: check for Mozilla-generated MID was not case-insensitive\r
+- fix: length of first body-line was not checked\r
+- fix: some problems with "From:" were not reported due to a\r
+       spelling error \r
+- fix: logging did'nt work correctly\r
+- fix: expanding of variables from .rc didn't work correctly\r
+\r
+V 1.2.01 (01-01-07)\r
+- minor code rewrite\r
+- new version numbers\r
+- chg: "-v*" replaces "-dn"\r
+- chg: improved expanding of variables from .rc\r
+- chg: sample.rc changed\r
+- new: logfile added (for debugging purposes)\r
+- new: forced check of a single posting (message-id) in any group\r
+- new: enhanced check for reply-to:\r
+- new: check for MX-/A-records in From:/Reply-To: (experimental only)\r
+- new: check for duplicate headers\r
+- new: program can be temporarily disabled via "*.disabled" file\r
+- new: killfile added\r
+\r
+V 1.1\r
+Build 00082501 BETA:\r
+- fix: bugfix for unquoted special characters in regexps\r
+- chg: sample.rc changed\r
+- chg: improved signature check (delimiter and length)\r
+- new: check for role accounts (only) in From:\r
+- chg: reporting of problems with From: / Reply-To: (renamed sections\r
+       in .rc-file)\r
+- chg: complete rewrite of &getmailaddres-parser\r
+Build 00081101 BETA:\r
+- fix: expanding of variables just worked once --> wrong output\r
+- fix: expanding of variables didn't recognize all variable names\r
+Build 00080901 BETA:\r
+- new: you may use variables in .rc-file / sample.rc changed accordingly\r
+- chg: MIDs conforming to draft-ietf-usefor-msg-id-alt-00, chapter\r
+       2.1.2, will be accepted (<unique%address@do.main>)\r
+- new: "$trigger_check verbose" in Subject will copy extract of\r
+       logfile to followup - you should set log level to 4 or above\r
+- chg: minor problems won't be reported anymore unless "$trigger_check"\r
+       is found in Subject ("$diaglevel")\r
+- chg: first line of posting is no longer checked for "$trigger_check"\r
+- chg: "nocheck" is no longer a trigger word\r
+- chg: .ini-file is rewritten after each group that has been checked\r
+- fix: Subject of followup could contain 8bit-chars\r
+- major code rewrite\r
+Build 00061301 BETA:\r
+- new: you may enter a special check-reply for every known newsreader\r
+       in the .rc-file that will be printed out after the "normal"\r
+       reply (.rc-file changed!)\r
+       syntax: [checkname-nr], e.g. [nomid-moz] or [nomid-gnus]\r
+- fix: newsreader from Staroffice was detected as Mozar^H^Hilla\r
+Build 00052101 BETA:\r
+- chg: check-reply for local-part/domain of From: (.rc-file changed!)\r
+- fix: regexp for legal characters in the local-part of From:\r
+Build 00051501 BETA:\r
+- fix: reading of .ini-file\r
+Build 00051401 BETA:\r
+- new: you may define the name of the .rc-file in the .ini-file\r
+- chg: format of .ini-file\r
+- fix: add trailing "/" to path, if needed\r
+- new: send "mode reader" command to both servers before starting\r
+- new: check local-part and domain of From: / Reply-to: (.rc-file changed!)\r
+- new: you may define a posting server different from the one you read\r
+       from (syntax of .ini-file changed!)\r
+- chg: improved handling of NNTP-errors\r
+- chg: default trigger string for check is now "check" (again) (.ini-file)\r
+- fix: 'nocheck' was ignored or mistaken for 'check' if trigger-expression\r
+       was changed from "\bcheck\b" to "check"\r
+- chg: Decoding (qp/Base64) is now done by modules from CPAN\r
+Build 000050501 BETA:\r
+- chg: parameters can now be given in any order you want\r
+Build 000050101 BETA:\r
+- fix: "uk" was missing in file "domains"\r
+- chg: warning for Mozilla-generated MID will appear only _if_ MID is\r
+       generated by Mozilla\r
+Build 00042401 BETA:\r
+- new: trigger strings for checking and ignoring (in auto-mode) may be defined\r
+       in .ini-file\r
+- chg: default trigger string for check is now "\bcheck\b" ---> check as a\r
+       single word\r
+- chg: better handling for check / ignore in subject and first line\r
+- fix: 'check' found in first line of body did not generate right\r
+       introduction for followup\r
+- new: debug levels 5/6\r
+Build 00042203 BETA:\r
+- new: you can have different .ini/.rc-files - new parameter when\r
+       starting artchk.pl\r
+- fix: URLs for identified newsreaders were not posted since 00042102\r
+Build 00042202 BETA:\r
+- new: settings for path to .ini/.rc/domains\r
+- fix: too long lines in signature were detected even if there was\r
+       no signature\r
+- new: "check" will also be found in first line of posting\r
+- chg: sample.rc ("check" in first line of body)\r
+- fix: base64-decoding (was still not working correctly)\r
+Build 00042201 BETA:\r
+- new: entries in *.rc-file may be deleted to skip checks\r
+- fix: sample.rc (spelling errors / some clarifications)\r
+Build 00042103 BETA:\r
+- fix: MID-FQDN-TLD-checking (was case-sensitive)\r
+Build 00042102 BETA:\r
+- new: decode base64-encoded bodies\r
+- new: check for Content-Transfer-Encoding: base64\r
+- fix: q/p-decoding was completely broken\r
+Build 00042101 BETA:\r
+- new: MID must have a FQDN with valid TLD\r
+- new: settings for debug level\r
+- new: you can specify the nntp-port in the *.ini-file (default: 119)\r
+Build 00042001 BETA:\r
+- fix: RegExp for checking From:-header (finally)\r
+Build 00041902 BETA:\r
+- fix: Date:-check\r
+Build 00041901 BETA:\r
+- new: check Date: for 4digit-year\r
+- fix: RegExp for checking From:-header\r
+- fix: don't compare From: and Reply-To:, but rather the addresses in them\r
+- chg: &getmailaddress redesigned\r
+Build 00041303 BETA:\r
+- fix: max. line length in sig is set to 80 chars\r
+Build 00041302 BETA:\r
+- new: send output via NNTP instead of copying files to the\r
+       news.out-directory.\r
+- chg: "replybymail" is not supported any more\r
+- chg: line length in sig not checked any more\r
+- fix: improved checking of sig-delimiter\r
+Build 00041301 BETA:\r
+- new: get input via NNTP instead of starting HAM.exe and reading\r
+       from files\r
+\r
+\r
+V 1.0\r
+Build 00040601 BETA:\r
+- fix: Date:-Header was invalid for single-digit day (wrong regexp)\r
+- fix: followups to crossposting were directed to wrong group\r
+Build 00032801 BETA:\r
+- fix: sigdelimiter-warning for OE was displayed even when sigdelimiter\r
+       was correct\r
+Build 00032501 BETA:\r
+- new: moved entries for last tested postings to .ini\r
+Build 00031901 BETA:\r
+- fix: problem with sig-delimiter: everything ending with "--" matched\r
+- fix: problem with q/p-decoding: whitespace at EOL was cut\r
+Build 00031802 BETA:\r
+- chg: killed Hamster-specific headers from header-copy in replies\r
+       by mail\r
+Build 00031801 BETA:\r
+- new: support for "replybymail" in the Subject:-line.\r
+- new: &getmailaddress\r
+- fix: RegExp for checking From:-header\r
+\r
+---------------------------------------------------------------------------\r
diff --git a/domains b/domains
new file mode 100644 (file)
index 0000000..57162a2
--- /dev/null
+++ b/domains
@@ -0,0 +1 @@
+ac ad ae af ag ai al am an ao aq ar as at au aw az ba bb bd be bf bg bh bi bj bm bn bo br bs bt bv bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv cx cy cz de dj dk dm do dz ec ee eg eh er es et fi fj fk fm fo fr ga gd ge gf gg gh gi gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md mg mh mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl pm pn pr ps pt pw py qa re ro ru rw sa sb sc sd se sg sh si sj sk sl sm sn so sr st sv sy sz tc td tf tg th tj tk tm tn to tp tr tt tv tw tz ua ug uk um us uy uz va vc ve vg vi vn vu wf ws ye yt yu za zm zw aero biz com coop edu gov info int mil museum name net org pro arpa bitnet uucp
\ No newline at end of file
diff --git a/readme.txt b/readme.txt
new file mode 100644 (file)
index 0000000..7cba1fd
--- /dev/null
@@ -0,0 +1,271 @@
+                    Automatic Article Checker\r
+    v1.6  Copyright (C) June 2, 1999  by Heinrich Schramm\r
+                  mailto:heinrich@schramm.com\r
+\r
+converted to perl by Wilfried Klaebe <wk@orion.toppoint.de>\r
+  (not really converted, more or less rewritten in perl)\r
+\r
+modified & enhanced (more or less rewritten ;-))\r
+  by Thomas Hochstein <THochstein@gmx.de> since March/April 2000\r
+(c) artchk.pl (mod.) January 06, 2001 by Thomas Hochstein\r
+\r
+Version: 1.2.01 BETA\r
+\r
+_________ ATTENTION please! - This is a BETA version! _________\r
+\r
+---------------------------------------------------------------------------\r
+This program is free software; you can redistribute it and/or modify it\r
+under the terms of the GNU General Public License as published by the Free\r
+Software Foundation; either version 2 of the License, or (at your option)\r
+any later version.\r
+This program is distributed in the hope that it will be useful, but\r
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\r
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\r
+for more details.\r
+---------------------------------------------------------------------------\r
+\r
+(1) REQUIREMENTS\r
+\r
+* Perl 5.x\r
+* the "News::NNTPClient"-module from CPAN.\r
+* the "MIME::QuotedPrint"-module from CPAN.\r
+* the "MIME::Base64"-module from CPAN.\r
+* the "Net::DNS"-module from CPAN.\r
+* a (local) NNTP-server\r
+\r
+(2) INSTALLATION\r
+\r
+* Install\r
+    - artchk.pl     # main program\r
+\r
+  and\r
+\r
+    - sample.ini    # .ini-file: server, port, user/password, groups, counter\r
+    - sample.rc     # .rc-file: customize headers / body of followups\r
+    - domains       # valid TLDs for MID-FQDNs\r
+\r
+  to the same directory.\r
+\r
+  The last three files must reside in the same directory as the first, or\r
+  you have to specify the path to them when invoking artchk.pl\r
+\r
+  You may specify different .ini-/.rc-files when invoking artchk.pl\r
+\r
+* Modify "sample.ini" to fit your needs and rename it to "artchk.pl.ini"\r
+  (default) or anything you like.\r
+\r
+  The .ini-file has to contain\r
+  - parameters\r
+  - the special word "checkgroups:" including the colon\r
+  - a list of groups to check\r
+\r
+  The parameters are written one on a line, "parametername = parameter".\r
+  Allowed parameters are:\r
+  - reader        : the newsserver (and port) you read the postings from,\r
+                    "server.name,port"\r
+                    This entry is necessary; you can drop the port.\r
+                    Default for port is 119.\r
+  - reader_user   : your username for authorization\r
+                    Default: (none) ---> no authorization\r
+  - reader_pass   : your password for authorization\r
+  - poster        : the newsserver (and port) you post to,\r
+                    "server.name,port"\r
+                    Default: [none] ---> post to the server you read from\r
+                    You can drop the port. Default for port is 119.\r
+  - reader_user   : your username for authorization\r
+                    Default: [none] ---> no authorization\r
+  - reader_pass   : your password for authorization\r
+  - trigger_check : a regular expression for the string that initiates\r
+                    a check when found in "Subject:".\r
+                    Default: check\r
+                    You should change "[header]" in the .rc-file accordingly!\r
+  - trigger_ignore: a regular expression for the string that stops a check\r
+                    in auto-mode (see below) when found in "Subject:" or\r
+                    first line of body.\r
+                    Default: (ignore)|(no[ ]*repl(y|(ies)))\r
+                    You should change "[header-auto]" in the .rc-file accordingly!\r
+  - rcfile        : the name of your .rc-file\r
+                    Default: [name of .ini-file]\r
+  - killfile      : the name of your .kill-file\r
+                    Default: [name of .ini-file]\r
+\r
+  You can place comment lines in between; they may NOT contain a "=".\r
+\r
+  The list of groups is in the following format:\r
+  - the name of a group to check\r
+  - a single space and a "y" or "n" to enable/disable auto-mode. Set it\r
+    to "n" - artchk will only post followups to postings with\r
+             trigger_check in the subject (but if trigger_check is\r
+             found, it _will_ post a followup, even if trigger_ignore is\r
+             also found)\r
+    to "y" - auto-mode; artchk will also post followups if it found\r
+             something to correct as long as trigger_ignore is _not_\r
+             found in the subject or the first line of the body and no\r
+            killfile-expresion matches\r
+\r
+  You may NOT place anything else after the magic word "checkgroups:".\r
+\r
+  - Example:\r
+       reader        = server.pro.vider,119\r
+       reader_user   = user\r
+       reader_pass   = pass\r
+       ---> We do not have another posting server.\r
+       checkgroups:\r
+       de.test y\r
+       de.alt.test n\r
+\r
+* Modify "sample.rc" to fit your needs and rename it to "artchk.pl.rc"\r
+  (default) or anything you like.\r
+\r
+    - [head]-Section:\r
+      Edit at least the "From:" header configuration.\r
+      Edit or delete the "Sender:" header.\r
+      Edit or delete the "Path:" header.\r
+      Edit or delete the "Reply-To:" header.\r
+      Add any other headers you like,\r
+      e.g. "X-Checkbot-Owner: My Name <my.name@do.main.invalid>"\r
+      Do _NOT_ insert a "Newsgroups:" header!\r
+      Do _NOT_ insert "Subject:", "Message-ID:", "References:" or "X-Artchk-Version:"!\r
+\r
+    - Edit the [header]-/[header-auto]- and/or [footer] text section if\r
+      you like. You should do that if you have changed the trigger_check/\r
+      trigger_ignore-settings!\r
+\r
+    - Later on, you can edit the other sections as you like. Please be\r
+      sure to have a look at the source code in this case to understand\r
+      how these sections are used.\r
+\r
+    - Later on, you may add sections with special tips for certain\r
+      newsclients. Those sections will be printed out immediately after\r
+      the standard-reply. They have the form of [standard-nr] with nr being\r
+      one of\r
+\r
+         oe (Outlook Express, all versions)\r
+         moz (Mozilla, all versions)\r
+         agent (Forté Agent _and_ Free Agent, all versions)\r
+         xnews (XNews, all versions)\r
+         gnus (Gnus, all versions)\r
+         macsoup (MacSoup, all versions)\r
+         slrn (slrn, all versions)\r
+         mpg (Microplanet's Gravity, all versions)\r
+        pine\r
+        xp (crosspoint)\r
+        pminews\r
+\r
+    - You may also delete sections - except for the following:\r
+      [head] [header] [header-auto] [footer] [intro] [allok] [nr]\r
+      The corresponding checks will then be skipped.\r
+\r
+* Attention! The sample files have CR/LF as linebreaks (DOS). On UNIX\r
+  machines, you'll have to convert the files to use just LF as EOL, for\r
+  example using "tr -d '\r' < sample.rc > sample.rc-unix".\r
+\r
+(3) HOW TO RUN artchk.pl\r
+\r
+* Just start it up. ;-)\r
+\r
+  - "perl artchk.pl" should do fine, but artchk.pl will also accept parameters:\r
+    perl artchk.pl -p<path> -n<name> -v[vvv] -l<logfile> -c<mid> --log\r
+\r
+  - -v[vvv]\r
+    Default: 0\r
+    Verbosity level from "0" to "4". See below.\r
+    e.g. "perl artchk.pl -vvv".\r
+\r
+  - -p\r
+    Default: (empty)\r
+    Path to your .ini-/.rc-/domains/.disabled/.kill/.log-file.\r
+    e.g. "perl artchk.pl -pc:\programme\artchk\".\r
+\r
+  - -n\r
+    Default: artchk.pl[.ini]\r
+    Name for your .ini-file. This name also applies for the .disabled-file.\r
+    See below.\r
+    e.g. "perl artchk.pl -nserver1".\r
+    \r
+  - -l\r
+    Default: (the name set via -n)[.log]\r
+    Name for the logfile (if activated). See "--log" below.\r
+    e.g. "perl artchk.pl -lartchk.log".\r
+\r
+  - -c\r
+    Default: (there is none)\r
+    Force check of a posting with "-c<message-id>".\r
+    e.g. "perl artchk.pl -c<176r23r2erwfwe@do.main>"\r
+\r
+  - --log\r
+    Activate logfile.\r
+\r
+  - artchk.pl will recognize the parameters regardless of their order.\r
+\r
+* artchk.pl will _not_ start if a file artchk.pl.disabled (or a file with\r
+  any other, depending on the "-n"-parameter) exists in the path given\r
+  with "-p".\r
+\r
+* Normally, it will read the .rc- and .ini-file and do a little bit (!)\r
+  of syntax-checking with them.\r
+\r
+* Then it'll connect to your server and check the first group. It'll get\r
+  every new article from there, check it and -possibly- generate a\r
+  followup and post it to that group. Then it'll do all other groups.\r
+  It will delete and rewrite (!) the .ini-file to keep the article\r
+  counter up to date.\r
+\r
+* It will check every posting unless it\r
+  - is not in a group containing "test"\r
+  - is a control message\r
+  - already is a checkbot answers (detected by MID)\r
+  - contains trigger_ignore in Keywords:\r
+  - doesn't contain trigger_check in Subject:\r
+    AND auto-mode is off\r
+\r
+  If auto-mode is on, it will also check postings without\r
+  trigger_check in Subject:, unless they\r
+\r
+  - contain trigger_ignore in Subject: or first line of body\r
+  - matcht the killfile (see below)\r
+\r
+* It will include an excerpt from the logfile if $trigger_check is\r
+  followed by "verbose".\r
+\r
+* The display output of artchk.pl can be more or less verbose.\r
+  \r
+  A debug level\r
+\r
+  of:           means:\r
+  0   - introduction/end + error messages\r
+  1   - 0 + configuration and summaries\r
+  2   - 1 + progress indicator\r
+  3   - 2 + NNTP status replies from server\r
+  4   - 3 + (debug-)output from check-routines\r
+\r
+  Default is 0.\r
+\r
+* You can add a killfile to exclude certain postings from auto-mode.\r
+  This file must have the name defined in your .ini-file and reside\r
+  in the path given with "-p".\r
+  It must have the following format:\r
+  headerfield = regular expression # comment\r
+  where headerfield is the name of any header field\r
+        regular expression is any regular expression\r
+       comment (anything from # to EOL) is a comment that is ignored\r
+\r
+  If the regular expression matches the content of the header field,\r
+  the posting is ignored in auto-mode. It is _not_ ignored if a check\r
+  is requested via trigger-check in the Subject:.\r
+\r
+---------------------------------------------------------------------------\r
+\r
+Please remember:\r
+\r
+(1) This is a BETA version. Report all bugs and suggestions to\r
+    <artchk@akallabeth.de>.\r
+\r
+(2) If you start running this bot in a non-local newsgroup, please send a\r
+    short notice to <artchk@akallabeth.de>. That will make it possible to\r
+    report bugs, problems and updates to you.\r
+\r
+(3) Please use this program with care and sense of responsibility!\r
+    Thank you.\r
+\r
+---------------------------------------------------------------------------
\ No newline at end of file
diff --git a/sample.ini b/sample.ini
new file mode 100644 (file)
index 0000000..c253eea
--- /dev/null
@@ -0,0 +1,7 @@
+reader        = localhost,119\r
+reader_user   = user\r
+reader_pass   = pass\r
+---> We do not have another posting server.\r
+checkgroups:\r
+de.test y\r
+de.alt.test n\r
diff --git a/sample.rc b/sample.rc
new file mode 100644 (file)
index 0000000..831a407
--- /dev/null
+++ b/sample.rc
@@ -0,0 +1,470 @@
+; #########################################################\r
+; # standard resource file for artchk.pl (mod.) V 1.2.01k #\r
+; #########################################################\r
+;\r
+;\r
+;\r
+; header / introduction(s)\r
+; ########################\r
+; --> you may _not_ delete one of those\r
+; --> you may _not_ use variables\r
+;\r
+;\r
+[head]\r
+;\r
+Path: your.do.main!checkbot\r
+From: Your-Domain Article Checker <checkbot@your.do.main.invalid>\r
+Sender: <checkbot-owner@your.do.main.invalid>\r
+Reply-To: <checkbot-owner@your.do.main.invalid>\r
+X-No-Archive: yes\r
+;\r
+[header]\r
+;\r
+- ignore - no reply - ignore - no reply - ignore - no reply - ignore -\r
+\r
+Dies ist ein automatisch generierter Antwortartikel. Er wurde von dem\r
+Programm "Article Checker" in der Annahme gepostet, daß der Autor des\r
+Originalartikels eine formale Überprüfung seines Artikels wünscht und\r
+deshalb das Wort "check" in irgendeiner Form im Subject angegeben hat.\r
+\r
+;\r
+[header-auto]\r
+;\r
+- ignore - no reply - ignore - no reply - ignore - no reply - ignore -\r
+\r
+Dies ist ein automatisch generierter Antwortartikel. Er  wurde  von  dem\r
+Programm "Article Checker" gepostet, um auf  denkbare  formale  Probleme\r
+des Originalartikels hinzuweisen.\r
+\r
+Das Wort "ignore" im Subject oder der ersten  Zeile  des  Bodies  unter-\r
+drückt diese automatische Antwort.\r
+\r
+;\r
+[header-forced]\r
+;\r
+- ignore - no reply - ignore - no reply - ignore - no reply - ignore -\r
+\r
+Dieser  Antwortartikel  enthält  eine  Bewertung   des   vorangegangenen\r
+Postings durch das Programm "Article Checker" im Hinblick  auf  mögliche\r
+formale Unkorrektheiten.\r
+\r
+;\r
+[nr]\r
+;\r
+\r
+Bei Fragen zur Bedienung Deines Newsreaders  wende  Dich  bitte  an  die\r
+passende Newsgruppe in de.comm.software.*\r
+\r
+Vorher  lies  aber  bitte  die  Handbücher,  FAQs  und  die   Texte   in\r
+news:de.newusers.infos bzw. im Web unter <http://www.kirchwitz.de/dni/>\r
+;\r
+[footer]\r
+;\r
+Viel Spaß noch im Usenet!\r
+;\r
+[allok]\r
+;\r
+\r
+   Ich konnte keine formalen Fehler in Deinem Artikel finden.\r
+   Allerdings bin auch ich nicht perfekt.  ;-)\r
+;\r
+[debug]\r
+;\r
+\r
+Auf Wunsch - Schlüsselwort "check verbose" im Subject - folgt hier\r
+der das geprüfte Posting betreffende Auszug aus dem Logfile:\r
+\r
+;\r
+[intro]\r
+;\r
+\r
+Folgendes ist mir an Deinem Artikel aufgefallen:\r
+;\r
+[violation]\r
+;\r
+In Deinem Artikel glaube ich Verstöße gegen  technische  Standards  über\r
+das Format von Usenet-Nachrichten festgestellt zu haben; diese sind  mit\r
+einem Stern ("*") gekennzeichnet. Andere Hinweise  werden  zur  besseren\r
+Unterscheidung mit einem Spiegelstrich ("-") eingeleitet.\r
+;\r
+;\r
+;\r
+; parts where you may _not_ use variables\r
+; #######################################\r
+; --> you _may_ delete one of those\r
+; --> you may _not_ use variables\r
+;\r
+;\r
+[duplicate]\r
+;\r
+In Deinem Posting kommen ein oder mehrere Header (Kopfzeilen)\r
+mehrfach vor; das ist vermutlich unbeabsichtigt. - Es handelt\r
+sich dabei um\r
+;\r
+[umlauts]\r
+;\r
+  Weitere Informationen zu Umlauten, Textkodierungen und\r
+  Zeichensatzdeklarationen findest Du unter:\r
+  <http://www.westfalen.de/paefken/de.newusers/umlaute-faq.txt>\r
+;\r
+[newscis]\r
+;\r
+Als Nutzer des kostenlosen Newsservers von individual.de|.net\r
+sind für Dich vielleicht auch die Konfigurationsbeispiele unter\r
+\r
+   <http://www.individual.de/config.html> (deutsch)\r
+   <http://www.individual.net/config.html> (englisch)\r
+\r
+hilfreich.\r
+;\r
+;\r
+;\r
+; generic parts\r
+; #############\r
+; --> you _may_ delete one of those\r
+; --> you _may_ use variables; those include:\r
+;     - all headers (%header, e.g. $header{'from'})\r
+;     - all headers after decoding (%header_decoded, e.g. $header_decoded{'from'})\r
+;     - local part of "From:" ($frlocpart)\r
+;     - domain part of "From:" ($frdompart)\r
+;     - local part of "Reply-To:" ($rplocpart)\r
+;     - domain part of "Reply-To:" ($rpdompart)\r
+;     - wrong sig delimiter ($wrongsig)\r
+;\r
+;\r
+[multipart]\r
+;\r
+- Du  hast  Deinen  Artikel  als  mehrteiliges  Posting  im  MIME-Format\r
+  verschickt (MIME-multipart). Das ist im allgemeinen ein Zeichen dafür,\r
+  daß an den Textteil Deines Postings noch etwas angehängt wird: sei  es\r
+  eine Wiederholung des Textes in einem anderen Format, bspw.  in  HTML,\r
+  sei es eine "Visitenkarte", sei es eine  digitale  Signatur  oder  gar\r
+  eine Datei.\r
+\r
+  Solche mehrteiligen Nachrichten sind generell eher unbeliebt  oder  in\r
+  Diskussionsgruppen gar unzulässig; Du solltest  Deinen  Newsreader  so\r
+  konfigurieren, daß er  Nachrichten  ausschließlich  einteilig  und  im\r
+  Klartext ("text/plain") abliefert.\r
+;\r
+[html]\r
+;\r
+- Anscheinend hast Du Deinen Artikel in HTML gepostet. Im Usenet ist die\r
+  Verwendung von  HTML  sehr  ungern  gesehen,  da  der  Umfang  dadurch\r
+  vervielfacht wird, ohne deutlich mehr  Informationen  zu  transportie-\r
+  ren. Außerdem können die meisten Newsreader kein HTML  interpretieren.\r
+  Um unnötigen Ärger zu vermeiden, solltest Du dies abstellen und Deinen\r
+  Newsreader  so  konfigurieren,  daß  er   Nachrichten   ausschließlich\r
+  einteilig und im Klartext ("text/plain") abliefert.\r
+;\r
+[vcard]\r
+;\r
+- An Deinem Artikel scheint eine (Visitenkarte) VCARD angehängt zu sein.\r
+  Im Usenet ist die Verwendung von VCARDs sehr ungern  gesehen,  da  sie\r
+  nur für wenige Leute  lesbar  bzw.  interessant  sind  und  sie  dafür\r
+  vergleichsweise viel Platz verbrauchen.\r
+\r
+  Die enthaltenen Informationen stehen entweder ohnehin im  Header  (den\r
+  "Kopfzeilen") Deines Beitrags, oder Du kannst Sie in  Deiner  Signatur\r
+  unterbringen.\r
+;\r
+[nocharset]\r
+;\r
+* Du verwendest Nicht-ASCII-Zeichen (z.B. Umlaute) in Deinem Artikel,\r
+  aber im Header fehlt die Deklaration des Zeichensatzes oder Du\r
+  verwendest als Zeichensatz "US-ASCII" (was keine Umlaute enthält).\r
+  Leser, die einen anderen Default-Zeichensatz auf ihrem Rechner\r
+  eingestellt haben, sehen deshalb anstelle der Umlaute nur\r
+  Schmierzeichen.\r
+\r
+  Eine gültige Deklaration sieht so aus:\r
+  Content-Type: text/plain; charset=iso-8859-1\r
+;\r
+[nocontenttransferenc]\r
+;\r
+* Du verwendest Nicht-ASCII-Zeichen (z.B. Umlaute), deklarierst aber\r
+  keine Kodierung.\r
+\r
+  Eine gültige Deklaration sieht so aus:\r
+  Content-Transfer-Encoding: 8bit\r
+;\r
+[base64]\r
+;\r
+- Du verwendest als (Umlaut-)Kodierung  für  den  Text  Deines  Postings\r
+  "Base64". Das ist generell _nicht_ zu empfehlen,  da  diese  Kodierung\r
+  für binäre Dateien vorgesehen und den Umfang Deines  Postings  um  ca.\r
+  ein Drittel vergrößert. Außerdem ist sie mit  bloßem  Auge  gar  nicht\r
+  mehr zu entziffern,  so  daß  Deine  Beiträge  für  Teilnehmer,  deren\r
+  Programme "Base64" nicht dekodieren können, gar nicht mehr lesbar ist.\r
+\r
+  Du solltest Deine Textkodierung daher auf\r
+\r
+       Content-Transfer-Encoding: 8bit\r
+\r
+  abändern.\r
+;\r
+[nomid]\r
+;\r
+* Deine Message-ID scheint nicht in Ordnung zu sein.\r
+  \r
+  Du verwendest: $header{'message-id'}\r
+\r
+  Eine Message-ID dient der eindeutigen  Identifikation  eines  Postings\r
+  und damit auch der Verhinderung  von  Duplikaten.  Sie  muß  die  Form\r
+  <eindeutiger_Teil>@<FQDN> haben. Dabei steht <FQDN> für einen komplet-\r
+  ten  Domainnamen,  der  dem  Erzeuger  der  Message-ID   zur   Nutzung\r
+  zugewiesen sein muß (also Dir, wenn Du selbst IDs für  Deine  Postings\r
+  erzeugen möchtest).  Eine  IP-Nummer  ist  an  dieser  Stelle  _nicht_\r
+  zulässig.\r
+\r
+  Für  nähere  Informationen   dazu   vergleiche   die   Message-ID-FAQ:\r
+  <http://www.hanau.net/usenet/faq/messageid.php>\r
+;\r
+[longlines]\r
+;\r
+- Kürze bitte Deine Zeilenlänge auf  etwa  72  Zeichen.  Dies  wird  als\r
+  höflich  angesehen,  da  nicht  alle  Newsreader  Zeilen   automatisch\r
+  umbrechen  bzw.  ein  automatischer   Zeilenumbruch   generell   nicht\r
+  vorgesehen ist. Du ermöglichst es auf diese Weise,  Deinen  Text  noch\r
+  mehrfach zu zitieren und  trotz  eingefügter  Zitatzeichen  unter  der\r
+  "magischen Grenze" von 80 Zeichen pro Zeile zu bleiben.\r
+\r
+  Abgesehen davon lassen sich kürzere Zeilen deutlich besser lesen; auch\r
+  Zeitungen drucken zum Beispiel nicht grundlos in Spalten.\r
+;\r
+[longlinesig]\r
+;\r
+- Auch in der Signatur sollten Deine Zeilen nicht länger als maximal  80\r
+  Zeichen sein. Das entspricht einer üblichen Grenze für den  Text-modus\r
+  und wird als  höflich  angesehen,  da  nicht  alle  Newsreader  Zeilen\r
+  automatisch umbrechen.\r
+;\r
+[8bitheader]\r
+;\r
+* Im Header (den "Kopfzeilen") Deines Artikels  sind  unkodierte  8-Bit-\r
+  Zeichen (also  Umlaute  und/oder  Sonderzeichen)  vorhanden.  Das  ist\r
+  unzulässig und kann dazu führen, daß dein Artikel von einigen Systemen\r
+  nicht befördert wird oder auf einigen Systemen nicht darstellbar  ist.\r
+  8-bit-Zeichen sind im Header  grundsätzlich  durch  Umschreibungen  zu\r
+  ersetzen, was vermutlich Dein  Newsreader  für  Dich  erledigen  kann.\r
+\r
+  Anderenfalls mußt Du  auf  Umlaute  etc.  im  Header  verzichten;  das\r
+  betrifft  sowohl  Subject: ("Betreff") wie  auch From: (Absenderangabe)\r
+  und alle anderen Headerzeilen.\r
+;\r
+[reply-to]\r
+;\r
+* Dein Reply-To:-Header\r
+       "$header_decoded{'reply-to'}"\r
+  scheint  syntaktisch  unkorrekt  bzw.  ungültig  zu  sein.  Vermutlich\r
+  entspricht der sog. "localpart" der Mailadresse (also der  Teil  links\r
+  vom "@") nicht den technischen Vorgaben. Häufig liegt das  daran,  daß\r
+  ein Punkt am  Ende  von  "localpart"  nicht  erlaubt  ist  und  Punkte\r
+  innerhalb eines angegebenen Namens nur dann zulässig  sind,  wenn  der\r
+  Name in Anführungszeichen steht.\r
+;\r
+[reply-to-domain]\r
+;\r
+* Die  Mailadresse  in  Deinem  Reply-To:-Header   ist   ungültig:   die\r
+  angegebene Domain\r
+       "$rpdompart"\r
+  existiert nicht oder nimmt jedenfalls keine Mail entgegen.\r
+;\r
+[reply-to-roles]\r
+;\r
+- Du verwendest\r
+       "$rplocpart"\r
+  als Teil Deiner Mail-Adresse im Reply-To:.\r
+\r
+  Dieser Begriff ist aber entweder ein sog. "Role-Account",  d.h.  nicht\r
+  zur  Verwendung  durch  einzelne  Personen,  sondern   für   bestimmte\r
+  Funktionen vorgesehen, oder aus sonstigen Gründen unüblich.\r
+\r
+  Es wäre daher ratsam, eine andere Mailadresse zu verwenden.\r
+;\r
+[replytofrom]\r
+;\r
+- Der Reply-To:-Header ist nur nötig, wenn darin  eine  andere  Addresse\r
+  als im From:-Header angegeben wird.\r
+;\r
+[sigdelimiter]\r
+;\r
+- Dein Signatur-Abtrenner  scheint nicht  dem  üblichen  Standard  "-- "\r
+  (Minus, Minus, Leerzeichen,  ohne  die  Anführungszeichen,  auf  einer\r
+  eigenen Zeile) zu entsprechen.\r
+\r
+  Vielmehr sieht er so aus: "$wrongsig"\r
+;\r
+[date]\r
+;\r
+* Dein Date:-Header scheint unkorrekt zu sein.\r
+\r
+  Du verwendest:  $header{'date'}\r
+\r
+  Korrekt wäre bspw.:  Wed, 12 Apr 2000 12:12:12\r
+  mit einer vierstelligen Angabe der Jahreszahl.\r
+;\r
+[from]\r
+;\r
+* Dein  From:-Header  entspricht   nicht   den   technischen   Vorgaben;\r
+  vermutlich enthält er nicht zulässige Zeichen.\r
+\r
+  Du verwendest:  $header_decoded{'from'}\r
+\r
+  Korrekt wäre :  Realname <localpart@do.main>\r
+          oder :  <localpart@do.main>\r
+          oder :  localpart@do.main (Realname)\r
+          oder :  localpart@do.main\r
+\r
+  Dabei müssen "Realname", "localpart" und "do.main" jeweils  bestimmten\r
+  Voraussetzungen  insbesondere  hinsichtlich  der   erlaubten   Zeichen\r
+  genügen. Insbesondere ist ein Punkt  am  Ende  von  "localpart"  nicht\r
+  erlaubt; und Punkte innerhalb von "Realname" sind nur  dann  zulässig,\r
+  wenn "Realname" in Anführungszeichen steht.\r
+;\r
+[from-domain]\r
+;\r
+* Die Mailadresse in Deinem From:-Header ist  ungültig:  die  angegebene\r
+  Domain\r
+       "$frdompart"\r
+  existiert nicht oder nimmt jedenfalls keine Mail entgegen.\r
+;\r
+[from-roles]\r
+;\r
+- Du verwendest\r
+       "$frlocpart"\r
+  als Teil Deiner Mail-Adresse im From:.\r
+\r
+  Dieser Begriff ist aber entweder ein sog. "Role-Account",  d.h.  nicht\r
+  zur  Verwendung  durch  einzelne  Personen,  sondern   für   bestimmte\r
+  Funktionen vorgesehen, oder aus sonstigen Gründen unüblich.\r
+\r
+  Es wäre daher ratsam, eine andere Mailadresse zu verwenden.\r
+;\r
+[noname]\r
+;\r
+- In Deinen From:-Header, also  die  Absenderangabe,  solltest  Du  noch\r
+  Deinen vollen Namen eintragen.\r
+\r
+  Dieser sollte die Form \r
+       Vorname 'Pseudonym' Nachname\r
+  haben.\r
+;\r
+[longsig]\r
+;\r
+- Deine Signatur ist anscheinend länger als die  üblichen  vier  Zeilen.\r
+  Dies wird von vielen als unhöflich angesehen und führt immer wieder zu\r
+  Streit. Du solltest sie entsprechend kürzen.\r
+;\r
+;\r
+;\r
+;\r
+; faqs for some readers\r
+; #####################\r
+; --> you _may_ delete one of those\r
+; --> you _may_ use variables\r
+;\r
+;\r
+[nr-known]\r
+;\r
+Hinweise und Tips speziell zu Deinem Newsreader findest Du in der FAQ unter\r
+   <http://www.thomas-huehn.de/usenet/newsreaderFAQ.txt>\r
+und auch unter\r
+;\r
+[oe]\r
+;\r
+   <http://oe-faq.de/> (deutsch)\r
+;\r
+[moz]\r
+;\r
+   <http://www.holgermetzger.de/faqmailnews.html> (dt.)\r
+   <http://home.t-online.de/home/Peter.Dobler/netscape.htm> (dt.)\r
+;\r
+[agent]\r
+;\r
+   <http://www.netandmore.de/faq/forte/> (dt.)\r
+   <http://www.soscha.de/faq/dcsfa-faq.txt>\r
+   <http://www.westfalen.de/paefken/forteagent/> (dt., veraltet)\r
+   <http://www.vibe.at/begriffe/ags-kurs.html> (dt.)\r
+;\r
+[xnews]\r
+;\r
+   <http://www.hreimers.de/Xnews/> (dt.)\r
+   <http://www.x501.de/xnews/faq.htm> (dt.)\r
+;\r
+[gnus]\r
+;\r
+   <http://linux01.gwdg.de/~steufel/enter.html> (dt.)\r
+   <http://www.ccs.neu.edu/software/contrib/gnus/> (engl.)\r
+   <http://www.gnus.org/manual.html> (engl.)\r
+\r
+;\r
+[macsoup]\r
+;\r
+   <http://www.snafu.de/~stk/macsoup/> (engl.)\r
+   <http://www.snafu.de/~stk/macsoup/resedit_hacks.html> (engl.)\r
+;\r
+[slrn]\r
+;\r
+   <http://www.slrn.org/> (engl.)\r
+;\r
+[trn]\r
+;\r
+   <http://www.OCF.Berkeley.EDU/help/usenet/trnint-3.3.html> (engl.)\r
+;\r
+;\r
+;\r
+;\r
+;\r
+; special parts for some problems with some readers\r
+; #################################################\r
+; --> will be printed AFTER the generic part (see above)\r
+; --> consists of the name of the generic part and the name of the reader:\r
+;     "[nomid-moz]" -> "[nomid]" + "[moz]"\r
+; --> you may add new ones by putting together the name of one of the generic\r
+;     parts (see above) and one of the readers (see above)\r
+; --> you _may_ delete one of those\r
+; --> you _may_ use variables\r
+;\r
+;\r
+[nomid-moz]\r
+;\r
+  In Deinem Fall liegt das Problem vermutlich in Deinem Newsreader,  der\r
+  von  Haus  aus  Message-IDs  fälschlicherweise  mit  der  Domain   der\r
+  Mailadresse generiert. Abhilfe schafft  es,  an  die  Mailadresse  ein\r
+  Leerzeichen anzuhängen; bitte wirf dazu auch einen Blick in die FAQs!\r
+\r
+  Solltest Du über  die  Domain  in  Deiner  Mailadresse  frei  verfügen\r
+  können, weil Du sie selbst registriert hast, kannst Du diesen  Hinweis\r
+  allerdings ignorieren.\r
+;\r
+[8bitheader-agent]\r
+;\r
+  Wenn Du über die Vollversion des Forté Agent verfügst, kannst  Du  das\r
+  abstellen, indem Du unter "Options  |  General  Preferences"  auf  dem\r
+  Reiter "Languages" rechts unten die  beiden  Kästchen  "MIME  headers"\r
+  ankreuzt.\r
+\r
+  Die kostenlose "Schnupperversion" Forté Free Agent kann  mit  Umlauten\r
+  gar nicht umgehen und ist daher für den Dauerbetrieb wenig geeignet.\r
+;\r
+[8bitheader-oe]\r
+;\r
+  Da Du offenbar Outlook Express nutzt,  solltest  Du  unter  "Extras  |\r
+  Optionen  |  Senden  |  Einstellungen"  den  Punkt  "8-bit-Zeichen  in\r
+  Kopfdaten zulassen" deaktivieren. Das mußt Du  jeweils  für  Mail  und\r
+  News getrennt  tun  -  in  beiden  Fällen  sind  8bit-Zeichen  nämlich\r
+  technisch unzulässig.\r
+;\r
+[sigdelimiter-oe]\r
+;\r
+  Das liegt vermutlich daran,  daß  Dein  Newsreader  das  abschließende\r
+  Leerzeichen vor dem Versand wieder löscht.\r
+;\r
+[sigdelimiter-moz]\r
+;\r
+  Vermutlich nutzt Du  den  HTML-Editor  für  Deine  Postings.  Versuche\r
+  einmal, ihn mit Edit -> Preferences -> Mail & Newsgroups -> Formatting\r
+  ->  Message  Formatting  ->  "Use  the  plain   text   editor..."   zu\r
+  deaktivieren.  Dann  sollte   Dein   Programm   automatisch   korrekte\r
+  Signaturtrenner setzen.\r
+;\r
This page took 0.037483 seconds and 4 git commands to generate.