X-Git-Url: https://code.th-h.de/?p=usenet%2Fyapfaq.git;a=blobdiff_plain;f=yapfaq.pl;h=d4114d4e7f7d50a05bc6a9c99cee65cb26b96c55;hp=0e141e52bcd81ceb3d60f633124454b65cb8cb4d;hb=HEAD;hpb=5a6670c7ff54e30051018ff1e333e314ffa0d2dc;ds=sidebyside diff --git a/yapfaq.pl b/yapfaq.pl old mode 100644 new mode 100755 index 0e141e5..d4114d4 --- a/yapfaq.pl +++ b/yapfaq.pl @@ -1,63 +1,43 @@ #! /usr/bin/perl -W # -# yapfaq Version 0.6 by Thomas Hochstein +# yapfaq Version 0.10 by Thomas Hochstein # (Original author: Marc Brockschmidt) # # This script posts any project described in its config-file. Most people # will use it in combination with cron(8). # # Copyright (C) 2003 Marc Brockschmidt -# Copyright (c) 2010 Thomas Hochstein +# Copyright (c) 2010-2017 Thomas Hochstein # # It can be redistributed and/or modified under the same terms under # which Perl itself is published. -my $Version = "0.6.2"; +our $VERSION = "0.10"; +# Please do not change this setting! +# You may override the default .rc file (.yapfaqrc) by using "-c .rc file" my $RCFile = '.yapfaqrc'; -my @ValidConfVars = ('NNTPServer','NNTPUser','NNTPPass','Sender','ConfigFile', - 'UsePGP','pgp','PGPVersion','PGPSigner','PGPPass', - 'PathtoPGPPass','pgpbegin','pgpend','pgptmpf','pgpheader'); +# Valid configuration variables for use in a .rc file +my @ValidConfVars = ('NNTPServer','NNTPUser','NNTPPass','Sender','ConfigFile','Program'); -################################### Defaults ################################## -my %Config = (NNTPServer => "localhost", +################################### Defaults ################################### +# Please do not change anything in here! +# Use a runtime configuration file (.yapfaqrc by default) to override defaults. +my %Config = (NNTPServer => "", NNTPUser => "", NNTPPass => "", Sender => "", ConfigFile => "yapfaq.cfg", - UsePGP => 0, - - ################################## PGP-Config ################################# - pgp => '/usr/bin/pgp', # path to pgp - PGPVersion => '2', # Use 2 for 2.X, 5 for PGP > 2.X and GPG for GPG - PGPSigner => '', # sign as who? - PGPPass => '', # pgp2 only - PathtoPGPPass => '', # pgp2, pgp5 and gpg - pgpbegin => '-----BEGIN PGP SIGNATURE-----', # Begin of PGP-Signature - pgpend => '-----END PGP SIGNATURE-----', # End of PGP-Signature - pgptmpf => 'pgptmp', # temporary file for PGP. - pgpheader => 'X-PGP-Sig'); - -my @PGPSignHeaders = ('From', 'Newsgroups', 'Subject', 'Control', - 'Supersedes', 'Followup-To', 'Date', 'Sender', 'Approved', - 'Message-ID', 'Reply-To', 'Cancel-Lock', 'Cancel-Key', - 'Also-Control', 'Distribution'); - -my @PGPorderheaders = ('from', 'newsgroups', 'subject', 'control', - 'supersedes', 'followup-To', 'date', 'organization', 'lines', - 'sender', 'approved', 'distribution', 'message-id', - 'references', 'reply-to', 'mime-version', 'content-type', - 'content-transfer-encoding', 'summary', 'keywords', 'cancel-lock', - 'cancel-key', 'also-control', 'x-pgp', 'user-agent'); - -############################# End of Configuration ############################# + Program => ""); + +################################# Main program ################################# use strict; use Net::NNTP; use Net::Domain qw(hostfqdn); use Date::Calc qw(Add_Delta_YM Add_Delta_Days Delta_Days Today); -use Fcntl ':flock'; # import LOCK_* constants use Getopt::Std; +$Getopt::Std::STANDARD_HELP_VERSION = 1; my ($TDY, $TDM, $TDD) = Today(); #TD: Today's date # read commandline options @@ -65,7 +45,7 @@ my %Options; getopts('Vhvpdt:f:c:s:', \%Options); # -V: print version / copyright information if ($Options{'V'}) { - print "$0 v $Version\nCopyright (c) 2003 Marc Brockschmidt \nCopyright (c) 2010 Thomas Hochstein \n"; + print "$0 v $VERSION\nCopyright (c) 2003 Marc Brockschmidt \nCopyright (c) 2010-2017 Thomas Hochstein \n"; print "This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself.\n"; exit(0); } @@ -85,6 +65,8 @@ if (-f $RCFile) { warn "$0: W: .rc file $RCFile does not exist!\n"; } +$Options{'s'} = $Config{'Program'} if (defined($Config{'Program'}) && $Config{'Program'} && !defined($Options{'s'})); + # read configuration (configured FAQs) my @Config; readconfig (\$Config{'ConfigFile'}, \@Config, \$Faq); @@ -100,7 +82,7 @@ foreach (@Config) { my ($ActName,$File,$PFreq,$Expire) =($$_{'name'},$$_{'file'},$$_{'posting-frequency'},$$_{'expires'}); my ($From,$Subject,$NG,$Fup2)=($$_{'from'},$$_{'subject'},$$_{'ngs'},$$_{'fup2'}); - my ($MIDF,$ReplyTo,$ExtHea)=($$_{'mid-format'},$$_{'reply-to'},$$_{'extraheader'}); + my ($MIDF,$ReplyTo,$Charset,$ExtHea)=($$_{'mid-format'},$$_{'reply-to'},$$_{'charset'},$$_{'extraheader'}); my ($Supersede) =($$_{'supersede'}); # -f: loop if not FAQ to post @@ -129,7 +111,7 @@ foreach (@Config) { if($Options{'d'}) { print "$ActName: Would be posted now (but running in simulation mode [$0 -d]).\n" if $Options{'v'}; } else { - postfaq(\$ActName,\$File,\$From,\$Subject,\$NG,\$Fup2,\$MIDF,\$ExtHea,\$Config{'Sender'},\$TDY,\$TDM,\$TDD,\$ReplyTo,\$SupersedeMID,\$Expire); + postfaq(\$ActName,\$File,\$From,\$Subject,\$NG,\$Fup2,\$MIDF,\$Charset,\$ExtHea,\$Config{'Sender'},\$TDY,\$TDM,\$TDD,\$ReplyTo,\$SupersedeMID,\$Expire); } } elsif($Options{'v'}) { print "$ActName: Nothing to do.\n"; @@ -166,7 +148,7 @@ sub readconfig{ my ($File, $Config, $Faq) = @_; my ($LastEntry, $Error, $i) = ('','',0); - print "Reading configuration.\n" if($Options{'v'}); + print "Reading configuration from $$File.\n" if($Options{'v'}); open FH, "<$$File" or die "$0: E: Can't open $$File: $!"; while () { @@ -207,10 +189,12 @@ sub readconfig{ $Error .= "E: The Posting-frequency for your project \"$$Config[$i]{'name'}\" is invalid.\n" } unless(!$$Config[$i]{'expires'} || $$Config[$i]{'expires'} =~ /^\s*\d+\s*[dwmy]\s*$/) { - warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n"; + warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n"; + $$Config[$i]{'expires'} = '3m'; # set default (3 month) if expires is unset or invalid } - unless(defined($$Config[$i]{'mid-format'}) && $$Config[$i]{'mid-format'} =~ /^<\S+\@\S{2,}\.\S{2,}>$/) { - warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" seems to be invalid - set to default.\n"; + unless(!$$Config[$i]{'mid-format'} || $$Config[$i]{'mid-format'} =~ /^<\S+\@(\S+\.)?\S{2,}\.\S{2,}>/) { + warn "$0: W: The Message-ID format for your project \"$$Config[$i]{'name'}\" seems to be invalid - set to default.\n"; + $$Config[$i]{'mid-format'} = '<%n-%y-%m-%d@'.hostfqdn.'>'; # set default if mid-format is invalid } } $Error .= "-" x 25 . 'program terminated' . "-" x 25 . "\n" if $Error; @@ -254,7 +238,7 @@ sub updatestatus { # It reads the data-file $File and then posts the article. sub postfaq { - my ($ActName,$File,$From,$Subject,$NG,$Fup2,$MIDF,$ExtraHeaders,$Sender,$TDY,$TDM,$TDD,$ReplyTo,$Supersedes,$Expire) = @_; + my ($ActName,$File,$From,$Subject,$NG,$Fup2,$MIDF,$Charset,$ExtraHeaders,$Sender,$TDY,$TDM,$TDD,$ReplyTo,$Supersedes,$Expire) = @_; my (@Header,@Body,$MID,$InRealBody,$LastModified); print "$$ActName: Preparing to post.\n" if($Options{'v'}); @@ -262,13 +246,15 @@ sub postfaq { #Prepare MID: $$TDM = ($$TDM < 10 && $$TDM !~ /^0/) ? "0" . $$TDM : $$TDM; $$TDD = ($$TDD < 10 && $$TDD !~ /^0/) ? "0" . $$TDD : $$TDD; + my $Timestamp = time; $MID = $$MIDF; - $MID = '<%n-%d.%m.%y@'.hostfqdn.'>' if !defined($MID); + $MID = '<%n-%y-%m-%d@'.hostfqdn.'>' if !defined($MID); # set to default if unset $MID =~ s/\%n/$$ActName/g; $MID =~ s/\%d/$$TDD/g; $MID =~ s/\%m/$$TDM/g; $MID =~ s/\%y/$$TDY/g; + $MID =~ s/\%t/$Timestamp/g; #Now get the body: open (FH, "<$$File"); @@ -276,7 +262,7 @@ sub postfaq { s/\r//; push (@Body, $_), next if $InRealBody; $InRealBody++ if /^$/; - $LastModified = $1 if /^Last-modified: (\S+)$/i; + $LastModified = $1 if /^Last-modified:\s*(\S+)\s*$/i; push @Body, $_; } close FH; @@ -305,11 +291,22 @@ sub postfaq { #Replace %LM by the content of the news.answer-pseudo-header Last-modified: if ($LastModified) { $$Subject =~ s/\%LM/$LastModified/; + } else { + $$Subject =~ s/[<\[{\(]?\%LM[>\]}\)]?//; } + # Set Charset + $$Charset = 'UTF-8' if !$$Charset; + my $ContentType = sprintf('text/plain; charset=%s',$$Charset); + # Test mode? if($Options{'t'} and $Options{'t'} !~ /console/i) { $$NG = $Options{'t'}; + $MID =~ s/@/-$Timestamp-test@/g; + $$ExtraHeaders .= "\n" if $$ExtraHeaders; + $$ExtraHeaders .= "X-Supersedes: $$Supersedes\n" if $$Supersedes; + $$ExtraHeaders .= "X-yapfaq-Remark: This is only a test message."; + undef $$Supersedes; } #Now create the complete Header: @@ -324,16 +321,15 @@ sub postfaq { push @Header, "Sender: $$Sender\n" if $$Sender; push @Header, "Mime-Version: 1.0\n"; push @Header, "Reply-To: $$ReplyTo\n" if $$ReplyTo; - push @Header, "Content-Type: text/plain; charset=ISO-8859-15\n"; + push @Header, "Content-Type: $ContentType\n"; push @Header, "Content-Transfer-Encoding: 8bit\n"; - push @Header, "User-Agent: yapfaq/$Version\n"; + push @Header, "User-Agent: yapfaq/$VERSION\n"; if ($$ExtraHeaders) { push @Header, "$_\n" for (split /\n/, $$ExtraHeaders); } - # sign article if $UsePGP is true - my @Article = ($Config{'UsePGP'})?@{signpgp(\@Header, \@Body)}:(@Header, "\n", @Body); - + my @Article = (@Header, "\n", @Body); + # post article print "$$ActName: Posting article ...\n" if($Options{'v'}); my $failure = post(\@Article); @@ -397,169 +393,6 @@ sub post { return $failure; } -#-------- sub getpgpcommand -# getpgpcommand generates the command to sign the message and returns it. -# -# Receives: -# - $PGPVersion: A scalar holding the PGPVersion -sub getpgpcommand { - my ($PGPVersion) = @_; - my $PGPCommand; - - if ($PGPVersion eq '2') { - if ($Config{'PathtoPGPPass'} && !$Config{'PGPPass'}) { - open (PGPPW, $Config{'PathtoPGPPass'}) or die "$0: E: Can't open $Config{'PathtoPGPPass'}: $!"; - Config{'$PGPPass'} = ; - close PGPPW; - } - - if (Config{'$PGPPass'}) { - $PGPCommand = "PGPPASS=\"".$Config{'PGPPass'}."\" ".$Config{'pgp'}." -u \"".$Config{'PGPSigner'}."\" +verbose=0 language='en' -saft <".$Config{'pgptmpf'}.".txt >".$Config{'pgptmpf'}.".txt.asc"; - } else { - die "$0: E: PGP-Passphrase is unknown!\n"; - } - } elsif ($PGPVersion eq '5') { - if ($Config{'PathtoPGPPass'}) { - $PGPCommand = "PGPPASSFD=2 ".$Config{'pgp'}."s -u \"".$Config{'PGPSigner'}."\" -t --armor -o ".$Config{'pgptmpf'}.".txt.asc -z -f < ".$Config{'pgptmpf'}.".txt 2<".$Config{'PathtoPGPPass'}; - } else { - die "$0: E: PGP-Passphrase is unknown!\n"; - } - } elsif ($PGPVersion =~ m/GPG/io) { - if (Config{'$PathtoPGPPass'}) { - $PGPCommand = $Config{'pgp'}." --digest-algo MD5 -a -u \"".$Config{'PGPSigner'}."\" -o ".$Config{'pgptmpf'}.".txt.asc --no-tty --batch --passphrase-fd 2 2<".$Config{'PathtoPGPPass'}." --clearsign ".$Config{'pgptmpf'}.".txt"; - } else { - die "$0: E: Passphrase is unknown!\n"; - } - } else { - die "$0: E: Unknown PGP-Version $PGPVersion!"; - } - return $PGPCommand; -} - - -#-------- sub signarticle -# signarticle signs an articel and returns a reference to an array -# containing the whole signed Message. -# -# Receives: -# - $HeaderAR: A reference to a array containing the articles headers. -# - $BodyR: A reference to an array containing the body. -# -# Returns: -# - $MessageRef: A reference to an array containing the whole message. -sub signpgp { - my ($HeaderAR, $BodyR) = @_; - my (@pgphead, @pgpbody, $pgphead, $pgpbody, $header, $signheaders, @signheaders, $currentheader, $HeaderR, $line); - - foreach my $line (@$HeaderAR) { - if ($line =~ /^(\S+):\s+(.*)$/s) { - $currentheader = $1; - $$HeaderR{lc($currentheader)} = "$1: $2"; - } else { - $$HeaderR{lc($currentheader)} .= $line; - } - } - - foreach (@PGPSignHeaders) { - if (defined($$HeaderR{lc($_)}) && $$HeaderR{lc($_)} =~ m/^[^\s:]+: .+/o) { - push @signheaders, $_; - } - } - - $pgpbody = join ("", @$BodyR); - - # Delete and create the temporary pgp-Files - unlink "$Config{'pgptmpf'}.txt"; - unlink "$Config{'pgptmpf'}.txt.asc"; - $signheaders = join(",", @signheaders); - - $pgphead = "X-Signed-Headers: $signheaders\n"; - foreach $header (@signheaders) { - if ($$HeaderR{lc($header)} =~ m/^[^\s:]+: (.+?)\n?$/so) { - $pgphead .= $header.": ".$1."\n"; - } - } - - open(FH, ">" . $Config{'pgptmpf'} . ".txt") or die "$0: E: can't open $Config{'pgptmpf'}: $!\n"; - print FH $pgphead, "\n", $pgpbody; - print FH "\n" if ($Config{'PGPVersion'} =~ m/GPG/io); # workaround a pgp/gpg incompatibility - should IMHO be fixed in pgpverify - close(FH) or warn "$0: W: Couldn't close TMP: $!\n"; - - # Start PGP, then read the signature; - my $PGPCommand = getpgpcommand($Config{'PGPVersion'}); - `$PGPCommand`; - - open (FH, "<" . $Config{'pgptmpf'} . ".txt.asc") or die "$0: E: can't open ".$Config{'pgptmpf'}.".txt.asc: $!\n"; - $/ = "$Config{'pgpbegin'}\n"; - $_ = ; - unless (m/\Q$Config{'pgpbegin'}\E$/o) { -# unlink $Config{'pgptmpf'} . ".txt"; -# unlink $Config{'pgptmpf'} . ".txt.asc"; - die "$0: E: $Config{'pgpbegin'} not found in ".$Config{'pgptmpf'}.".txt.asc\n" - } - unlink($Config{'pgptmpf'} . ".txt") or warn "$0: W: Couldn't unlink $Config{'pgptmpf'}.txt: $!\n"; - - $/ = "\n"; - $_ = ; - unless (m/^Version: (\S+)(?:\s(\S+))?/o) { - unlink $Config{'pgptmpf'} . ".txt"; - unlink $Config{'pgptmpf'} . ".txt.asc"; - die "$0: E: didn't find PGP Version line where expected.\n"; - } - - if (defined($2)) { - $$HeaderR{$Config{'pgpheader'}} = $1."-".$2." ".$signheaders; - } else { - $$HeaderR{$Config{'pgpheader'}} = $1." ".$signheaders; - } - - do { # skip other pgp headers like - $_ = ; # "charset:"||"comment:" until empty line - } while ! /^$/; - - while () { - chomp; - last if /^\Q$Config{'pgpend'}\E$/; - $$HeaderR{$Config{'pgpheader'}} .= "\n\t$_"; - } - - $$HeaderR{$Config{'pgpheader'}} .= "\n" unless ($$HeaderR{$Config{'pgpheader'}} =~ /\n$/s); - - $_ = ; - unless (eof(FH)) { - unlink $Config{'pgptmpf'} . ".txt"; - unlink $Config{'pgptmpf'} . ".txt.asc"; - die "$0: E: unexpected data following $Config{'pgpend'}\n"; - } - close(FH); - unlink "$Config{'pgptmpf'}.txt.asc"; - - my $tmppgpheader = $Config{'pgpheader'} . ": " . $$HeaderR{$Config{'pgpheader'}}; - delete $$HeaderR{$Config{'pgpheader'}}; - - @pgphead = (); - foreach $header (@PGPorderheaders) { - if ($$HeaderR{$header} && $$HeaderR{$header} ne "\n") { - push(@pgphead, "$$HeaderR{$header}"); - delete $$HeaderR{$header}; - } - } - - foreach $header (keys %$HeaderR) { - if ($$HeaderR{$header} && $$HeaderR{$header} ne "\n") { - push(@pgphead, "$$HeaderR{$header}"); - delete $$HeaderR{$header}; - } - } - - push @pgphead, ("X-PGP-Key: " . $Config{'PGPSigner'} . "\n"), $tmppgpheader; - undef $tmppgpheader; - - @pgpbody = split /$/m, $pgpbody; - my @pgpmessage = (@pgphead, "\n", @pgpbody); - return \@pgpmessage; -} - __END__ ################################ Documentation ################################# @@ -570,7 +403,7 @@ yapfaq - Post Usenet FAQs I<(yet another postfaq)> =head1 SYNOPSIS -B [B<-hvpd>] [B<-t> I | CONSOLE] [B<-f> I] [B<-s> I] [B<-c> I<.rc file>] +B [B<-Vhvpd>] [B<-t> I | CONSOLE] [B<-f> I] [B<-s> I] [B<-c> I<.rc file>] =head1 REQUIREMENTS @@ -635,7 +468,7 @@ or Ionths> or Iears>. This value must be set. -=item B = I