From 353fe932bac09e5485944c6e5d7c4ac5b68353d8 Mon Sep 17 00:00:00 2001 From: Thomas Hochstein Date: Fri, 15 Jan 2010 09:02:12 +0100 Subject: [PATCH 1/1] Initial commit. Signed-off-by: Thomas Hochstein --- artchk.pl | 1239 +++++++++++++++++++++++++++++++++++++++++++++++++++ changes.txt | 169 +++++++ domains | 1 + readme.txt | 271 +++++++++++ sample.ini | 7 + sample.rc | 470 +++++++++++++++++++ 6 files changed, 2157 insertions(+) create mode 100644 artchk.pl create mode 100644 changes.txt create mode 100644 domains create mode 100644 readme.txt create mode 100644 sample.ini create mode 100644 sample.rc diff --git a/artchk.pl b/artchk.pl new file mode 100644 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 +# (not really converted, more or less rewritten in perl) +# +# modified & enhanced +# by Thomas Hochstein 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 + +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 .\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 () { + 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=; + $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 =
; # $wholeheader is global! + @header = split(/\n/,$wholeheader); # @header is global! + undef $/; + $wholebody =
; # $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 (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 , 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=~/^?$/) { + $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 ].\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=; + 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=; + 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(/ /,)); + @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 = ; + $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(/ /,)); + 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 index 0000000..c53433d --- /dev/null +++ b/changes.txt @@ -0,0 +1,169 @@ +Changes: (Version History) + +V 1.2 +V 1.2.02 (not released yet) +- new: direct feeding mode (experimental) (1.2.01k) +- new: "pedantic" mode; all violations will be reported in auto-mode +- chg: RFC-violations marked with "*" instead of "-" in output (1.2.01k) +- chg: only RFC-violations will be reported in auto-mode; + everything else is considered a minor problem (1.2.01k) +- chg: "quoted/printable" no longer checked (1.2.01k) +- chg: new "artchk.pl.disabled" will disable _all_ instances +- chg: sample.rc corrected (new URLs) +- fix: $diaglevel was not set for problems in From:/Reply-To: (1.2.01k) +- fix: signature delimiter in first line of posting wasn't checked (1.2.01i) +- fix: alternative postingserver could run in timeout (1.2.01g) +- fix: "mode reader" must be send _before_ authentification (1.2.01g) +- fix: default for $ininame contained full path (1.2.01g) +- fix: check for Mozilla-generated MID was not case-insensitive +- fix: length of first body-line was not checked +- fix: some problems with "From:" were not reported due to a + spelling error +- fix: logging did'nt work correctly +- fix: expanding of variables from .rc didn't work correctly + +V 1.2.01 (01-01-07) +- minor code rewrite +- new version numbers +- chg: "-v*" replaces "-dn" +- chg: improved expanding of variables from .rc +- chg: sample.rc changed +- new: logfile added (for debugging purposes) +- new: forced check of a single posting (message-id) in any group +- new: enhanced check for reply-to: +- new: check for MX-/A-records in From:/Reply-To: (experimental only) +- new: check for duplicate headers +- new: program can be temporarily disabled via "*.disabled" file +- new: killfile added + +V 1.1 +Build 00082501 BETA: +- fix: bugfix for unquoted special characters in regexps +- chg: sample.rc changed +- chg: improved signature check (delimiter and length) +- new: check for role accounts (only) in From: +- chg: reporting of problems with From: / Reply-To: (renamed sections + in .rc-file) +- chg: complete rewrite of &getmailaddres-parser +Build 00081101 BETA: +- fix: expanding of variables just worked once --> wrong output +- fix: expanding of variables didn't recognize all variable names +Build 00080901 BETA: +- new: you may use variables in .rc-file / sample.rc changed accordingly +- chg: MIDs conforming to draft-ietf-usefor-msg-id-alt-00, chapter + 2.1.2, will be accepted () +- new: "$trigger_check verbose" in Subject will copy extract of + logfile to followup - you should set log level to 4 or above +- chg: minor problems won't be reported anymore unless "$trigger_check" + is found in Subject ("$diaglevel") +- chg: first line of posting is no longer checked for "$trigger_check" +- chg: "nocheck" is no longer a trigger word +- chg: .ini-file is rewritten after each group that has been checked +- fix: Subject of followup could contain 8bit-chars +- major code rewrite +Build 00061301 BETA: +- new: you may enter a special check-reply for every known newsreader + in the .rc-file that will be printed out after the "normal" + reply (.rc-file changed!) + syntax: [checkname-nr], e.g. [nomid-moz] or [nomid-gnus] +- fix: newsreader from Staroffice was detected as Mozar^H^Hilla +Build 00052101 BETA: +- chg: check-reply for local-part/domain of From: (.rc-file changed!) +- fix: regexp for legal characters in the local-part of From: +Build 00051501 BETA: +- fix: reading of .ini-file +Build 00051401 BETA: +- new: you may define the name of the .rc-file in the .ini-file +- chg: format of .ini-file +- fix: add trailing "/" to path, if needed +- new: send "mode reader" command to both servers before starting +- new: check local-part and domain of From: / Reply-to: (.rc-file changed!) +- new: you may define a posting server different from the one you read + from (syntax of .ini-file changed!) +- chg: improved handling of NNTP-errors +- chg: default trigger string for check is now "check" (again) (.ini-file) +- fix: 'nocheck' was ignored or mistaken for 'check' if trigger-expression + was changed from "\bcheck\b" to "check" +- chg: Decoding (qp/Base64) is now done by modules from CPAN +Build 000050501 BETA: +- chg: parameters can now be given in any order you want +Build 000050101 BETA: +- fix: "uk" was missing in file "domains" +- chg: warning for Mozilla-generated MID will appear only _if_ MID is + generated by Mozilla +Build 00042401 BETA: +- new: trigger strings for checking and ignoring (in auto-mode) may be defined + in .ini-file +- chg: default trigger string for check is now "\bcheck\b" ---> check as a + single word +- chg: better handling for check / ignore in subject and first line +- fix: 'check' found in first line of body did not generate right + introduction for followup +- new: debug levels 5/6 +Build 00042203 BETA: +- new: you can have different .ini/.rc-files - new parameter when + starting artchk.pl +- fix: URLs for identified newsreaders were not posted since 00042102 +Build 00042202 BETA: +- new: settings for path to .ini/.rc/domains +- fix: too long lines in signature were detected even if there was + no signature +- new: "check" will also be found in first line of posting +- chg: sample.rc ("check" in first line of body) +- fix: base64-decoding (was still not working correctly) +Build 00042201 BETA: +- new: entries in *.rc-file may be deleted to skip checks +- fix: sample.rc (spelling errors / some clarifications) +Build 00042103 BETA: +- fix: MID-FQDN-TLD-checking (was case-sensitive) +Build 00042102 BETA: +- new: decode base64-encoded bodies +- new: check for Content-Transfer-Encoding: base64 +- fix: q/p-decoding was completely broken +Build 00042101 BETA: +- new: MID must have a FQDN with valid TLD +- new: settings for debug level +- new: you can specify the nntp-port in the *.ini-file (default: 119) +Build 00042001 BETA: +- fix: RegExp for checking From:-header (finally) +Build 00041902 BETA: +- fix: Date:-check +Build 00041901 BETA: +- new: check Date: for 4digit-year +- fix: RegExp for checking From:-header +- fix: don't compare From: and Reply-To:, but rather the addresses in them +- chg: &getmailaddress redesigned +Build 00041303 BETA: +- fix: max. line length in sig is set to 80 chars +Build 00041302 BETA: +- new: send output via NNTP instead of copying files to the + news.out-directory. +- chg: "replybymail" is not supported any more +- chg: line length in sig not checked any more +- fix: improved checking of sig-delimiter +Build 00041301 BETA: +- new: get input via NNTP instead of starting HAM.exe and reading + from files + + +V 1.0 +Build 00040601 BETA: +- fix: Date:-Header was invalid for single-digit day (wrong regexp) +- fix: followups to crossposting were directed to wrong group +Build 00032801 BETA: +- fix: sigdelimiter-warning for OE was displayed even when sigdelimiter + was correct +Build 00032501 BETA: +- new: moved entries for last tested postings to .ini +Build 00031901 BETA: +- fix: problem with sig-delimiter: everything ending with "--" matched +- fix: problem with q/p-decoding: whitespace at EOL was cut +Build 00031802 BETA: +- chg: killed Hamster-specific headers from header-copy in replies + by mail +Build 00031801 BETA: +- new: support for "replybymail" in the Subject:-line. +- new: &getmailaddress +- fix: RegExp for checking From:-header + +--------------------------------------------------------------------------- diff --git a/domains b/domains new file mode 100644 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 index 0000000..7cba1fd --- /dev/null +++ b/readme.txt @@ -0,0 +1,271 @@ + Automatic Article Checker + v1.6 Copyright (C) June 2, 1999 by Heinrich Schramm + mailto:heinrich@schramm.com + +converted to perl by Wilfried Klaebe + (not really converted, more or less rewritten in perl) + +modified & enhanced (more or less rewritten ;-)) + by Thomas Hochstein since March/April 2000 +(c) artchk.pl (mod.) January 06, 2001 by Thomas Hochstein + +Version: 1.2.01 BETA + +_________ ATTENTION please! - This is 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. +--------------------------------------------------------------------------- + +(1) REQUIREMENTS + +* Perl 5.x +* the "News::NNTPClient"-module from CPAN. +* the "MIME::QuotedPrint"-module from CPAN. +* the "MIME::Base64"-module from CPAN. +* the "Net::DNS"-module from CPAN. +* a (local) NNTP-server + +(2) INSTALLATION + +* Install + - artchk.pl # main program + + and + + - sample.ini # .ini-file: server, port, user/password, groups, counter + - sample.rc # .rc-file: customize headers / body of followups + - domains # valid TLDs for MID-FQDNs + + to the same directory. + + The last three files must reside in the same directory as the first, or + you have to specify the path to them when invoking artchk.pl + + You may specify different .ini-/.rc-files when invoking artchk.pl + +* Modify "sample.ini" to fit your needs and rename it to "artchk.pl.ini" + (default) or anything you like. + + The .ini-file has to contain + - parameters + - the special word "checkgroups:" including the colon + - a list of groups to check + + The parameters are written one on a line, "parametername = parameter". + Allowed parameters are: + - reader : the newsserver (and port) you read the postings from, + "server.name,port" + This entry is necessary; you can drop the port. + Default for port is 119. + - reader_user : your username for authorization + Default: (none) ---> no authorization + - reader_pass : your password for authorization + - poster : the newsserver (and port) you post to, + "server.name,port" + Default: [none] ---> post to the server you read from + You can drop the port. Default for port is 119. + - reader_user : your username for authorization + Default: [none] ---> no authorization + - reader_pass : your password for authorization + - trigger_check : a regular expression for the string that initiates + a check when found in "Subject:". + Default: check + You should change "[header]" in the .rc-file accordingly! + - trigger_ignore: a regular expression for the string that stops a check + in auto-mode (see below) when found in "Subject:" or + first line of body. + Default: (ignore)|(no[ ]*repl(y|(ies))) + You should change "[header-auto]" in the .rc-file accordingly! + - rcfile : the name of your .rc-file + Default: [name of .ini-file] + - killfile : the name of your .kill-file + Default: [name of .ini-file] + + You can place comment lines in between; they may NOT contain a "=". + + The list of groups is in the following format: + - the name of a group to check + - a single space and a "y" or "n" to enable/disable auto-mode. Set it + to "n" - artchk will only post followups to postings with + trigger_check in the subject (but if trigger_check is + found, it _will_ post a followup, even if trigger_ignore is + also found) + to "y" - auto-mode; artchk will also post followups if it found + something to correct as long as trigger_ignore is _not_ + found in the subject or the first line of the body and no + killfile-expresion matches + + You may NOT place anything else after the magic word "checkgroups:". + + - Example: + reader = server.pro.vider,119 + reader_user = user + reader_pass = pass + ---> We do not have another posting server. + checkgroups: + de.test y + de.alt.test n + +* Modify "sample.rc" to fit your needs and rename it to "artchk.pl.rc" + (default) or anything you like. + + - [head]-Section: + Edit at least the "From:" header configuration. + Edit or delete the "Sender:" header. + Edit or delete the "Path:" header. + Edit or delete the "Reply-To:" header. + Add any other headers you like, + e.g. "X-Checkbot-Owner: My Name " + Do _NOT_ insert a "Newsgroups:" header! + Do _NOT_ insert "Subject:", "Message-ID:", "References:" or "X-Artchk-Version:"! + + - Edit the [header]-/[header-auto]- and/or [footer] text section if + you like. You should do that if you have changed the trigger_check/ + trigger_ignore-settings! + + - Later on, you can edit the other sections as you like. Please be + sure to have a look at the source code in this case to understand + how these sections are used. + + - Later on, you may add sections with special tips for certain + newsclients. Those sections will be printed out immediately after + the standard-reply. They have the form of [standard-nr] with nr being + one of + + oe (Outlook Express, all versions) + moz (Mozilla, all versions) + agent (Forté Agent _and_ Free Agent, all versions) + xnews (XNews, all versions) + gnus (Gnus, all versions) + macsoup (MacSoup, all versions) + slrn (slrn, all versions) + mpg (Microplanet's Gravity, all versions) + pine + xp (crosspoint) + pminews + + - You may also delete sections - except for the following: + [head] [header] [header-auto] [footer] [intro] [allok] [nr] + The corresponding checks will then be skipped. + +* Attention! The sample files have CR/LF as linebreaks (DOS). On UNIX + machines, you'll have to convert the files to use just LF as EOL, for + example using "tr -d '\r' < sample.rc > sample.rc-unix". + +(3) HOW TO RUN artchk.pl + +* Just start it up. ;-) + + - "perl artchk.pl" should do fine, but artchk.pl will also accept parameters: + perl artchk.pl -p -n -v[vvv] -l -c --log + + - -v[vvv] + Default: 0 + Verbosity level from "0" to "4". See below. + e.g. "perl artchk.pl -vvv". + + - -p + Default: (empty) + Path to your .ini-/.rc-/domains/.disabled/.kill/.log-file. + e.g. "perl artchk.pl -pc:\programme\artchk\". + + - -n + Default: artchk.pl[.ini] + Name for your .ini-file. This name also applies for the .disabled-file. + See below. + e.g. "perl artchk.pl -nserver1". + + - -l + Default: (the name set via -n)[.log] + Name for the logfile (if activated). See "--log" below. + e.g. "perl artchk.pl -lartchk.log". + + - -c + Default: (there is none) + Force check of a posting with "-c". + e.g. "perl artchk.pl -c<176r23r2erwfwe@do.main>" + + - --log + Activate logfile. + + - artchk.pl will recognize the parameters regardless of their order. + +* artchk.pl will _not_ start if a file artchk.pl.disabled (or a file with + any other, depending on the "-n"-parameter) exists in the path given + with "-p". + +* Normally, it will read the .rc- and .ini-file and do a little bit (!) + of syntax-checking with them. + +* Then it'll connect to your server and check the first group. It'll get + every new article from there, check it and -possibly- generate a + followup and post it to that group. Then it'll do all other groups. + It will delete and rewrite (!) the .ini-file to keep the article + counter up to date. + +* It will check every posting unless it + - is not in a group containing "test" + - is a control message + - already is a checkbot answers (detected by MID) + - contains trigger_ignore in Keywords: + - doesn't contain trigger_check in Subject: + AND auto-mode is off + + If auto-mode is on, it will also check postings without + trigger_check in Subject:, unless they + + - contain trigger_ignore in Subject: or first line of body + - matcht the killfile (see below) + +* It will include an excerpt from the logfile if $trigger_check is + followed by "verbose". + +* The display output of artchk.pl can be more or less verbose. + + A debug level + + of: means: + 0 - introduction/end + error messages + 1 - 0 + configuration and summaries + 2 - 1 + progress indicator + 3 - 2 + NNTP status replies from server + 4 - 3 + (debug-)output from check-routines + + Default is 0. + +* You can add a killfile to exclude certain postings from auto-mode. + This file must have the name defined in your .ini-file and reside + in the path given with "-p". + It must have the following format: + headerfield = regular expression # comment + where headerfield is the name of any header field + regular expression is any regular expression + comment (anything from # to EOL) is a comment that is ignored + + If the regular expression matches the content of the header field, + the posting is ignored in auto-mode. It is _not_ ignored if a check + is requested via trigger-check in the Subject:. + +--------------------------------------------------------------------------- + +Please remember: + +(1) This is a BETA version. Report all bugs and suggestions to + . + +(2) If you start running this bot in a non-local newsgroup, please send a + short notice to . That will make it possible to + report bugs, problems and updates to you. + +(3) Please use this program with care and sense of responsibility! + Thank you. + +--------------------------------------------------------------------------- \ No newline at end of file diff --git a/sample.ini b/sample.ini new file mode 100644 index 0000000..c253eea --- /dev/null +++ b/sample.ini @@ -0,0 +1,7 @@ +reader = localhost,119 +reader_user = user +reader_pass = pass +---> We do not have another posting server. +checkgroups: +de.test y +de.alt.test n diff --git a/sample.rc b/sample.rc new file mode 100644 index 0000000..831a407 --- /dev/null +++ b/sample.rc @@ -0,0 +1,470 @@ +; ######################################################### +; # standard resource file for artchk.pl (mod.) V 1.2.01k # +; ######################################################### +; +; +; +; header / introduction(s) +; ######################## +; --> you may _not_ delete one of those +; --> you may _not_ use variables +; +; +[head] +; +Path: your.do.main!checkbot +From: Your-Domain Article Checker +Sender: +Reply-To: +X-No-Archive: yes +; +[header] +; +- ignore - no reply - ignore - no reply - ignore - no reply - ignore - + +Dies ist ein automatisch generierter Antwortartikel. Er wurde von dem +Programm "Article Checker" in der Annahme gepostet, daß der Autor des +Originalartikels eine formale Überprüfung seines Artikels wünscht und +deshalb das Wort "check" in irgendeiner Form im Subject angegeben hat. + +; +[header-auto] +; +- ignore - no reply - ignore - no reply - ignore - no reply - ignore - + +Dies ist ein automatisch generierter Antwortartikel. Er wurde von dem +Programm "Article Checker" gepostet, um auf denkbare formale Probleme +des Originalartikels hinzuweisen. + +Das Wort "ignore" im Subject oder der ersten Zeile des Bodies unter- +drückt diese automatische Antwort. + +; +[header-forced] +; +- ignore - no reply - ignore - no reply - ignore - no reply - ignore - + +Dieser Antwortartikel enthält eine Bewertung des vorangegangenen +Postings durch das Programm "Article Checker" im Hinblick auf mögliche +formale Unkorrektheiten. + +; +[nr] +; + +Bei Fragen zur Bedienung Deines Newsreaders wende Dich bitte an die +passende Newsgruppe in de.comm.software.* + +Vorher lies aber bitte die Handbücher, FAQs und die Texte in +news:de.newusers.infos bzw. im Web unter +; +[footer] +; +Viel Spaß noch im Usenet! +; +[allok] +; + + Ich konnte keine formalen Fehler in Deinem Artikel finden. + Allerdings bin auch ich nicht perfekt. ;-) +; +[debug] +; + +Auf Wunsch - Schlüsselwort "check verbose" im Subject - folgt hier +der das geprüfte Posting betreffende Auszug aus dem Logfile: + +; +[intro] +; + +Folgendes ist mir an Deinem Artikel aufgefallen: +; +[violation] +; +In Deinem Artikel glaube ich Verstöße gegen technische Standards über +das Format von Usenet-Nachrichten festgestellt zu haben; diese sind mit +einem Stern ("*") gekennzeichnet. Andere Hinweise werden zur besseren +Unterscheidung mit einem Spiegelstrich ("-") eingeleitet. +; +; +; +; parts where you may _not_ use variables +; ####################################### +; --> you _may_ delete one of those +; --> you may _not_ use variables +; +; +[duplicate] +; +In Deinem Posting kommen ein oder mehrere Header (Kopfzeilen) +mehrfach vor; das ist vermutlich unbeabsichtigt. - Es handelt +sich dabei um +; +[umlauts] +; + Weitere Informationen zu Umlauten, Textkodierungen und + Zeichensatzdeklarationen findest Du unter: + +; +[newscis] +; +Als Nutzer des kostenlosen Newsservers von individual.de|.net +sind für Dich vielleicht auch die Konfigurationsbeispiele unter + + (deutsch) + (englisch) + +hilfreich. +; +; +; +; generic parts +; ############# +; --> you _may_ delete one of those +; --> you _may_ use variables; those include: +; - all headers (%header, e.g. $header{'from'}) +; - all headers after decoding (%header_decoded, e.g. $header_decoded{'from'}) +; - local part of "From:" ($frlocpart) +; - domain part of "From:" ($frdompart) +; - local part of "Reply-To:" ($rplocpart) +; - domain part of "Reply-To:" ($rpdompart) +; - wrong sig delimiter ($wrongsig) +; +; +[multipart] +; +- Du hast Deinen Artikel als mehrteiliges Posting im MIME-Format + verschickt (MIME-multipart). Das ist im allgemeinen ein Zeichen dafür, + daß an den Textteil Deines Postings noch etwas angehängt wird: sei es + eine Wiederholung des Textes in einem anderen Format, bspw. in HTML, + sei es eine "Visitenkarte", sei es eine digitale Signatur oder gar + eine Datei. + + Solche mehrteiligen Nachrichten sind generell eher unbeliebt oder in + Diskussionsgruppen gar unzulässig; Du solltest Deinen Newsreader so + konfigurieren, daß er Nachrichten ausschließlich einteilig und im + Klartext ("text/plain") abliefert. +; +[html] +; +- Anscheinend hast Du Deinen Artikel in HTML gepostet. Im Usenet ist die + Verwendung von HTML sehr ungern gesehen, da der Umfang dadurch + vervielfacht wird, ohne deutlich mehr Informationen zu transportie- + ren. Außerdem können die meisten Newsreader kein HTML interpretieren. + Um unnötigen Ärger zu vermeiden, solltest Du dies abstellen und Deinen + Newsreader so konfigurieren, daß er Nachrichten ausschließlich + einteilig und im Klartext ("text/plain") abliefert. +; +[vcard] +; +- An Deinem Artikel scheint eine (Visitenkarte) VCARD angehängt zu sein. + Im Usenet ist die Verwendung von VCARDs sehr ungern gesehen, da sie + nur für wenige Leute lesbar bzw. interessant sind und sie dafür + vergleichsweise viel Platz verbrauchen. + + Die enthaltenen Informationen stehen entweder ohnehin im Header (den + "Kopfzeilen") Deines Beitrags, oder Du kannst Sie in Deiner Signatur + unterbringen. +; +[nocharset] +; +* Du verwendest Nicht-ASCII-Zeichen (z.B. Umlaute) in Deinem Artikel, + aber im Header fehlt die Deklaration des Zeichensatzes oder Du + verwendest als Zeichensatz "US-ASCII" (was keine Umlaute enthält). + Leser, die einen anderen Default-Zeichensatz auf ihrem Rechner + eingestellt haben, sehen deshalb anstelle der Umlaute nur + Schmierzeichen. + + Eine gültige Deklaration sieht so aus: + Content-Type: text/plain; charset=iso-8859-1 +; +[nocontenttransferenc] +; +* Du verwendest Nicht-ASCII-Zeichen (z.B. Umlaute), deklarierst aber + keine Kodierung. + + Eine gültige Deklaration sieht so aus: + Content-Transfer-Encoding: 8bit +; +[base64] +; +- Du verwendest als (Umlaut-)Kodierung für den Text Deines Postings + "Base64". Das ist generell _nicht_ zu empfehlen, da diese Kodierung + für binäre Dateien vorgesehen und den Umfang Deines Postings um ca. + ein Drittel vergrößert. Außerdem ist sie mit bloßem Auge gar nicht + mehr zu entziffern, so daß Deine Beiträge für Teilnehmer, deren + Programme "Base64" nicht dekodieren können, gar nicht mehr lesbar ist. + + Du solltest Deine Textkodierung daher auf + + Content-Transfer-Encoding: 8bit + + abändern. +; +[nomid] +; +* Deine Message-ID scheint nicht in Ordnung zu sein. + + Du verwendest: $header{'message-id'} + + Eine Message-ID dient der eindeutigen Identifikation eines Postings + und damit auch der Verhinderung von Duplikaten. Sie muß die Form + @ haben. Dabei steht für einen komplet- + ten Domainnamen, der dem Erzeuger der Message-ID zur Nutzung + zugewiesen sein muß (also Dir, wenn Du selbst IDs für Deine Postings + erzeugen möchtest). Eine IP-Nummer ist an dieser Stelle _nicht_ + zulässig. + + Für nähere Informationen dazu vergleiche die Message-ID-FAQ: + +; +[longlines] +; +- Kürze bitte Deine Zeilenlänge auf etwa 72 Zeichen. Dies wird als + höflich angesehen, da nicht alle Newsreader Zeilen automatisch + umbrechen bzw. ein automatischer Zeilenumbruch generell nicht + vorgesehen ist. Du ermöglichst es auf diese Weise, Deinen Text noch + mehrfach zu zitieren und trotz eingefügter Zitatzeichen unter der + "magischen Grenze" von 80 Zeichen pro Zeile zu bleiben. + + Abgesehen davon lassen sich kürzere Zeilen deutlich besser lesen; auch + Zeitungen drucken zum Beispiel nicht grundlos in Spalten. +; +[longlinesig] +; +- Auch in der Signatur sollten Deine Zeilen nicht länger als maximal 80 + Zeichen sein. Das entspricht einer üblichen Grenze für den Text-modus + und wird als höflich angesehen, da nicht alle Newsreader Zeilen + automatisch umbrechen. +; +[8bitheader] +; +* Im Header (den "Kopfzeilen") Deines Artikels sind unkodierte 8-Bit- + Zeichen (also Umlaute und/oder Sonderzeichen) vorhanden. Das ist + unzulässig und kann dazu führen, daß dein Artikel von einigen Systemen + nicht befördert wird oder auf einigen Systemen nicht darstellbar ist. + 8-bit-Zeichen sind im Header grundsätzlich durch Umschreibungen zu + ersetzen, was vermutlich Dein Newsreader für Dich erledigen kann. + + Anderenfalls mußt Du auf Umlaute etc. im Header verzichten; das + betrifft sowohl Subject: ("Betreff") wie auch From: (Absenderangabe) + und alle anderen Headerzeilen. +; +[reply-to] +; +* Dein Reply-To:-Header + "$header_decoded{'reply-to'}" + scheint syntaktisch unkorrekt bzw. ungültig zu sein. Vermutlich + entspricht der sog. "localpart" der Mailadresse (also der Teil links + vom "@") nicht den technischen Vorgaben. Häufig liegt das daran, daß + ein Punkt am Ende von "localpart" nicht erlaubt ist und Punkte + innerhalb eines angegebenen Namens nur dann zulässig sind, wenn der + Name in Anführungszeichen steht. +; +[reply-to-domain] +; +* Die Mailadresse in Deinem Reply-To:-Header ist ungültig: die + angegebene Domain + "$rpdompart" + existiert nicht oder nimmt jedenfalls keine Mail entgegen. +; +[reply-to-roles] +; +- Du verwendest + "$rplocpart" + als Teil Deiner Mail-Adresse im Reply-To:. + + Dieser Begriff ist aber entweder ein sog. "Role-Account", d.h. nicht + zur Verwendung durch einzelne Personen, sondern für bestimmte + Funktionen vorgesehen, oder aus sonstigen Gründen unüblich. + + Es wäre daher ratsam, eine andere Mailadresse zu verwenden. +; +[replytofrom] +; +- Der Reply-To:-Header ist nur nötig, wenn darin eine andere Addresse + als im From:-Header angegeben wird. +; +[sigdelimiter] +; +- Dein Signatur-Abtrenner scheint nicht dem üblichen Standard "-- " + (Minus, Minus, Leerzeichen, ohne die Anführungszeichen, auf einer + eigenen Zeile) zu entsprechen. + + Vielmehr sieht er so aus: "$wrongsig" +; +[date] +; +* Dein Date:-Header scheint unkorrekt zu sein. + + Du verwendest: $header{'date'} + + Korrekt wäre bspw.: Wed, 12 Apr 2000 12:12:12 + mit einer vierstelligen Angabe der Jahreszahl. +; +[from] +; +* Dein From:-Header entspricht nicht den technischen Vorgaben; + vermutlich enthält er nicht zulässige Zeichen. + + Du verwendest: $header_decoded{'from'} + + Korrekt wäre : Realname + oder : + oder : localpart@do.main (Realname) + oder : localpart@do.main + + Dabei müssen "Realname", "localpart" und "do.main" jeweils bestimmten + Voraussetzungen insbesondere hinsichtlich der erlaubten Zeichen + genügen. Insbesondere ist ein Punkt am Ende von "localpart" nicht + erlaubt; und Punkte innerhalb von "Realname" sind nur dann zulässig, + wenn "Realname" in Anführungszeichen steht. +; +[from-domain] +; +* Die Mailadresse in Deinem From:-Header ist ungültig: die angegebene + Domain + "$frdompart" + existiert nicht oder nimmt jedenfalls keine Mail entgegen. +; +[from-roles] +; +- Du verwendest + "$frlocpart" + als Teil Deiner Mail-Adresse im From:. + + Dieser Begriff ist aber entweder ein sog. "Role-Account", d.h. nicht + zur Verwendung durch einzelne Personen, sondern für bestimmte + Funktionen vorgesehen, oder aus sonstigen Gründen unüblich. + + Es wäre daher ratsam, eine andere Mailadresse zu verwenden. +; +[noname] +; +- In Deinen From:-Header, also die Absenderangabe, solltest Du noch + Deinen vollen Namen eintragen. + + Dieser sollte die Form + Vorname 'Pseudonym' Nachname + haben. +; +[longsig] +; +- Deine Signatur ist anscheinend länger als die üblichen vier Zeilen. + Dies wird von vielen als unhöflich angesehen und führt immer wieder zu + Streit. Du solltest sie entsprechend kürzen. +; +; +; +; +; faqs for some readers +; ##################### +; --> you _may_ delete one of those +; --> you _may_ use variables +; +; +[nr-known] +; +Hinweise und Tips speziell zu Deinem Newsreader findest Du in der FAQ unter + +und auch unter +; +[oe] +; + (deutsch) +; +[moz] +; + (dt.) + (dt.) +; +[agent] +; + (dt.) + + (dt., veraltet) + (dt.) +; +[xnews] +; + (dt.) + (dt.) +; +[gnus] +; + (dt.) + (engl.) + (engl.) + +; +[macsoup] +; + (engl.) + (engl.) +; +[slrn] +; + (engl.) +; +[trn] +; + (engl.) +; +; +; +; +; +; special parts for some problems with some readers +; ################################################# +; --> will be printed AFTER the generic part (see above) +; --> consists of the name of the generic part and the name of the reader: +; "[nomid-moz]" -> "[nomid]" + "[moz]" +; --> you may add new ones by putting together the name of one of the generic +; parts (see above) and one of the readers (see above) +; --> you _may_ delete one of those +; --> you _may_ use variables +; +; +[nomid-moz] +; + In Deinem Fall liegt das Problem vermutlich in Deinem Newsreader, der + von Haus aus Message-IDs fälschlicherweise mit der Domain der + Mailadresse generiert. Abhilfe schafft es, an die Mailadresse ein + Leerzeichen anzuhängen; bitte wirf dazu auch einen Blick in die FAQs! + + Solltest Du über die Domain in Deiner Mailadresse frei verfügen + können, weil Du sie selbst registriert hast, kannst Du diesen Hinweis + allerdings ignorieren. +; +[8bitheader-agent] +; + Wenn Du über die Vollversion des Forté Agent verfügst, kannst Du das + abstellen, indem Du unter "Options | General Preferences" auf dem + Reiter "Languages" rechts unten die beiden Kästchen "MIME headers" + ankreuzt. + + Die kostenlose "Schnupperversion" Forté Free Agent kann mit Umlauten + gar nicht umgehen und ist daher für den Dauerbetrieb wenig geeignet. +; +[8bitheader-oe] +; + Da Du offenbar Outlook Express nutzt, solltest Du unter "Extras | + Optionen | Senden | Einstellungen" den Punkt "8-bit-Zeichen in + Kopfdaten zulassen" deaktivieren. Das mußt Du jeweils für Mail und + News getrennt tun - in beiden Fällen sind 8bit-Zeichen nämlich + technisch unzulässig. +; +[sigdelimiter-oe] +; + Das liegt vermutlich daran, daß Dein Newsreader das abschließende + Leerzeichen vor dem Versand wieder löscht. +; +[sigdelimiter-moz] +; + Vermutlich nutzt Du den HTML-Editor für Deine Postings. Versuche + einmal, ihn mit Edit -> Preferences -> Mail & Newsgroups -> Formatting + -> Message Formatting -> "Use the plain text editor..." zu + deaktivieren. Dann sollte Dein Programm automatisch korrekte + Signaturtrenner setzen. +; -- 2.20.1