Bump version number to 0.91.
[usenet/yapfaq.git] / yapfaq.pl
1 #! /usr/bin/perl -W
2 #
3 # yapfaq Version 0.91 by Thomas Hochstein
4 # (Original author: Marc Brockschmidt)
5 #
6 # This script posts any project described in its config-file. Most people
7 # will use it in combination with cron(8).
8
9 # Copyright (C) 2003 Marc Brockschmidt <marc@marcbrockschmidt.de>
10 # Copyright (c) 2010 Thomas Hochstein <thh@inter.net>
11 #
12 # It can be redistributed and/or modified under the same terms under 
13 # which Perl itself is published.
14
15 our $VERSION = "0.91";
16
17 # Please do not change this setting!
18 # You may override the default .rc file (.yapfaqrc) by using "-c .rc file"
19 my $RCFile = '.yapfaqrc';
20 # Valid configuration variables for use in a .rc file
21 my @ValidConfVars = ('NNTPServer','NNTPUser','NNTPPass','Sender','ConfigFile','Program');
22
23 ################################### Defaults ###################################
24 # Please do not change anything in here!
25 # Use a runtime configuration file (.yapfaqrc by default) to override defaults.
26 my %Config = (NNTPServer => "",
27               NNTPUser   => "",
28               NNTPPass   => "",
29               Sender     => "",
30               ConfigFile => "yapfaq.cfg",
31               Program    => "");
32
33 ################################# Main program #################################
34
35 use strict;
36 use Net::NNTP;
37 use Net::Domain qw(hostfqdn);
38 use Date::Calc qw(Add_Delta_YM Add_Delta_Days Delta_Days Today);
39 use Getopt::Std;
40 $Getopt::Std::STANDARD_HELP_VERSION = 1;
41 my ($TDY, $TDM, $TDD) = Today(); #TD: Today's date
42
43 # read commandline options
44 my %Options;
45 getopts('Vhvpdt:f:c:s:', \%Options);
46 # -V: print version / copyright information
47 if ($Options{'V'}) {
48   print "$0 v $VERSION\nCopyright (c) 2003 Marc Brockschmidt <marc\@marcbrockschmidt.de>\nCopyright (c) 2010 Thomas Hochstein <thh\@inter.net>\n";
49   print "This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself.\n";
50   exit(0);
51 }
52 # -h: feed myself to perldoc
53 if ($Options{'h'}) {
54   exec ('perldoc', $0);
55   exit(0);
56 };
57 # -f: set $Faq
58 my ($Faq) = $Options{'f'} if ($Options{'f'});
59
60 # read runtime configuration (configuration variables)
61 $RCFile = $Options{'c'} if ($Options{'c'});
62 if (-f $RCFile) {
63   readrc (\$RCFile,\%Config);
64 } else {
65   warn "$0: W: .rc file $RCFile does not exist!\n";
66 }
67
68 $Options{'s'} = $Config{'Program'} if (defined($Config{'Program'}) && $Config{'Program'} && !defined($Options{'s'}));
69
70 # read configuration (configured FAQs)
71 my @Config;
72 readconfig (\$Config{'ConfigFile'}, \@Config, \$Faq);
73
74 # for each FAQ:
75 # - parse configuration
76 # - read status data
77 # - if FAQ is due: call postfaq()
78 foreach (@Config) { 
79   my ($LPD,$LPM,$LPY) = (01, 01, 0001);  #LP: Last posting-date
80   my ($NPY,$NPM,$NPD);                   #NP: Next posting-date
81   my $SupersedeMID;
82   
83   my ($ActName,$File,$PFreq,$Expire) =($$_{'name'},$$_{'file'},$$_{'posting-frequency'},$$_{'expires'});
84   my ($From,$Subject,$NG,$Fup2)=($$_{'from'},$$_{'subject'},$$_{'ngs'},$$_{'fup2'});
85   my ($MIDF,$ReplyTo,$ExtHea)=($$_{'mid-format'},$$_{'reply-to'},$$_{'extraheader'});
86   my ($Supersede)            =($$_{'supersede'});
87
88   # -f: loop if not FAQ to post
89   next if (defined($Faq) && $ActName ne $Faq);
90         
91   # read status data
92   if (open (FH, "<$File.cfg")) {
93     while(<FH>){
94       if (/##;; Lastpost:\s*(\d{1,2})\.(\d{1,2})\.(\d{2}(\d{2})?)/){
95         ($LPD, $LPM, $LPY) = ($1, $2, $3);
96       } elsif (/^##;;\s*LastMID:\s*(<\S+@\S+>)\s*$/) {
97         $SupersedeMID = $1;
98       }
99     }
100     close FH;
101   } else { 
102     warn "$0: W: Couldn't open $File.cfg: $!\n";
103   }
104
105   $SupersedeMID = "" unless $Supersede;
106
107   ($NPY,$NPM,$NPD) = calcdelta ($LPY,$LPM,$LPD,$PFreq);
108
109   # if FAQ is due: get it out
110   if (Delta_Days($NPY,$NPM,$NPD,$TDY,$TDM,$TDD) >= 0 or ($Options{'p'})) {
111     if($Options{'d'}) {
112           print "$ActName: Would be posted now (but running in simulation mode [$0 -d]).\n" if $Options{'v'};
113         } else {
114       postfaq(\$ActName,\$File,\$From,\$Subject,\$NG,\$Fup2,\$MIDF,\$ExtHea,\$Config{'Sender'},\$TDY,\$TDM,\$TDD,\$ReplyTo,\$SupersedeMID,\$Expire);
115         }
116   } elsif($Options{'v'}) {
117     print "$ActName: Nothing to do.\n";
118   }
119 }
120
121 exit;
122
123 #################################### readrc ####################################
124 # Takes a filename and the reference to an array which contains the valid options
125
126 sub readrc{
127   my ($File, $Config) = @_;
128
129   print "Reading $$File.\n" if($Options{'v'});
130
131   open FH, "<$$File" or die "$0: Can't open $$File: $!";
132   while (<FH>) {
133     if (/^\s*(\S+)\s*=\s*'?(.*?)'?\s*(#.*$|$)/) {
134       if (grep(/$1/,@ValidConfVars)) {
135         $$Config{$1} = $2 if $2 ne '';
136       } else {
137         warn "$0: W: $1 is not a valid configuration variable (reading from $$File)\n";
138       }
139     }
140   }
141 }
142
143 ################################## readconfig ##################################
144 # Takes a filename, a reference to an array, which will hold hashes with
145 # the data from $File, and - optionally - the name of the (single) FAQ to post
146
147 sub readconfig{
148   my ($File, $Config, $Faq) = @_;
149   my ($LastEntry, $Error, $i) = ('','',0);
150
151   print "Reading configuration from $$File.\n" if($Options{'v'});
152
153   open FH, "<$$File" or die "$0: E: Can't open $$File: $!";
154   while (<FH>) {
155     next if (defined($$Faq) && !/^\s*=====\s*$/ && defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} ne $$Faq );
156     if (/^(\s*(\S+)\s*=\s*'?(.*?)'?\s*(#.*$|$)|^(.*?)'?\s*(#.*$|$))/ && not /^\s*$/) {
157       $LastEntry = lc($2) if $2;
158       $$Config[$i]{$LastEntry} .= $3 if $3;  
159       $$Config[$i]{$LastEntry} .= "\n$5" if $5 && $5;
160     } 
161     if (/^\s*=====\s*$/) {
162       $i++;
163     }
164   }
165   close FH;
166
167   #Check saved values:
168   for $i (0..$i){
169     next if (defined($$Faq) && defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} ne $$Faq );
170     unless(defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} =~ /^\S+$/) {
171       $Error .= "E: The name of your project \"$$Config[$i]{'name'}\" is not defined or contains whitespaces.\n"
172     }
173     unless(defined($$Config[$i]{'file'}) && -f $$Config[$i]{'file'}) {
174       $Error .= "E: The file to post for your project \"$$Config[$i]{'name'}\" is not defined or does not exist.\n"
175     }
176     unless(defined($$Config[$i]{'from'}) && $$Config[$i]{'from'} =~ /\S+\@(\S+\.)?\S{2,}\.\S{2,}/) {
177       $Error .= "E: The From header for your project \"$$Config[$i]{'name'}\" seems to be incorrect.\n"
178     }
179     unless(defined($$Config[$i]{'ngs'}) && $$Config[$i]{'ngs'} =~ /^\S+$/) {
180       $Error .= "E: The Newsgroups header for your project \"$$Config[$i]{'name'}\" is not defined or contains whitespaces.\n"
181     }
182     unless(defined($$Config[$i]{'subject'})) {
183       $Error .= "E: The Subject header for your project \"$$Config[$i]{'name'}\" is not defined.\n"
184     }
185     unless(!$$Config[$i]{'fup2'} || $$Config[$i]{'fup2'} =~ /^\S+$/) {
186       $Error .= "E: The Followup-To header for your project \"$$Config[$i]{'name'}\" contains whitespaces.\n"
187     }
188     unless(defined($$Config[$i]{'posting-frequency'}) && $$Config[$i]{'posting-frequency'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
189       $Error .= "E: The Posting-frequency for your project \"$$Config[$i]{'name'}\" is invalid.\n"
190     }
191     unless(!$$Config[$i]{'expires'} || $$Config[$i]{'expires'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
192       warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n";
193       $$Config[$i]{'expires'} = '3m'; # set default (3 month) if expires is unset or invalid
194     }
195     unless(!$$Config[$i]{'mid-format'} || $$Config[$i]{'mid-format'} =~ /^<\S+\@(\S+\.)?\S{2,}\.\S{2,}>/) {
196       warn "$0: W: The Message-ID format for your project \"$$Config[$i]{'name'}\" seems to be invalid - set to default.\n";
197       $$Config[$i]{'mid-format'} = '<%n-%y-%m-%d@'.hostfqdn.'>'; # set default if mid-format is invalid
198     }
199   }
200   $Error .= "-" x 25 . 'program terminated' . "-" x 25 . "\n" if $Error;
201   die $Error if $Error;
202 }
203
204 ################################# calcdelta #################################
205 # Takes a date (year,  month and day) and a time period (1d, 1w, 1m, 1y, ...)
206 # and adds the latter to the former
207
208 sub calcdelta {
209   my ($Year, $Month, $Day, $Period) = @_;
210   my ($NYear, $NMonth, $NDay);
211
212   if ($Period =~ /(\d+)\s*([dw])/) { # Is counted in days or weeks: Use Add_Delta_Days.
213     ($NYear, $NMonth, $NDay) = Add_Delta_Days($Year, $Month, $Day, (($2 eq "w")?$1 * 7: $1 * 1));
214   } elsif ($Period =~ /(\d+)\s*([my])/) { #Is counted in months or years: Use Add_Delta_YM
215     ($NYear, $NMonth, $NDay) = Add_Delta_YM($Year, $Month, $Day, (($2 eq "m")?(0,$1):($1,0)));
216   }
217   return ($NYear, $NMonth, $NDay);
218 }
219
220 ################################ updatestatus ###############################
221 # Takes a MID and a status file name
222 # and writes status information to disk
223
224 sub updatestatus {
225   my ($ActName, $File, $date, $MID) = @_;
226   
227   print "$$ActName: Save status information.\n" if($Options{'v'});
228
229   open (FH, ">$$File.cfg") or die "$0: E: Can't open $$File.cfg: $!";
230   print FH "##;; Lastpost: $date\n";
231   print FH "##;; LastMID: $MID\n";
232   close FH;
233 }
234   
235 ################################## postfaq ##################################
236 # Takes a filename and many other vars.
237 #
238 # It reads the data-file $File and then posts the article.
239
240 sub postfaq {
241   my ($ActName,$File,$From,$Subject,$NG,$Fup2,$MIDF,$ExtraHeaders,$Sender,$TDY,$TDM,$TDD,$ReplyTo,$Supersedes,$Expire) = @_;
242   my (@Header,@Body,$MID,$InRealBody,$LastModified);
243
244   print "$$ActName: Preparing to post.\n" if($Options{'v'});
245   
246   #Prepare MID:
247   $$TDM = ($$TDM < 10 && $$TDM !~ /^0/) ? "0" . $$TDM : $$TDM;
248   $$TDD = ($$TDD < 10 && $$TDD !~ /^0/) ? "0" . $$TDD : $$TDD;
249   my $Timestamp = time;
250
251   $MID = $$MIDF;
252   $MID = '<%n-%y-%m-%d@'.hostfqdn.'>' if !defined($MID); # set to default if unset
253   $MID =~ s/\%n/$$ActName/g;
254   $MID =~ s/\%d/$$TDD/g;
255   $MID =~ s/\%m/$$TDM/g;
256   $MID =~ s/\%y/$$TDY/g;
257   $MID =~ s/\%t/$Timestamp/g;
258
259   #Now get the body:
260   open (FH, "<$$File");
261   while (<FH>){  
262     s/\r//;
263     push (@Body, $_), next if $InRealBody;
264     $InRealBody++ if /^$/;
265     $LastModified = $1 if /^Last-modified:\s*(\S+)\s*$/i;
266     push @Body, $_;
267   }
268   close FH;
269   push @Body, "\n" if ($Body[-1] ne "\n");
270
271   #Create Date- and Expires-Header:
272   my @time = localtime;
273   my $ss =  ($time[0]<10) ? "0" . $time[0] : $time[0];
274   my $mm =  ($time[1]<10) ? "0" . $time[1] : $time[1];
275   my $hh =  ($time[2]<10) ? "0" . $time[2] : $time[2];
276   my $day = $time[3];
277   my $month = ($time[4]+1<10) ? "0" . ($time[4]+1) : $time[4]+1;
278   my $monthN = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$time[4]];
279   my $wday = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat")[$time[6]];
280   my $year = (1900 + $time[5]);
281   my $tz = $time[8] ? " +0200" : " +0100";
282
283   $$Expire = '3m' if !$$Expire; # set default if unset: 3 month
284
285   my ($expY,$expM,$expD) = calcdelta ($year,$month,$day,$$Expire);
286   my $expmonthN = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$expM-1];
287
288   my $date = "$day $monthN $year " . $hh . ":" . $mm . ":" . $ss . $tz;
289   my $expdate = "$expD $expmonthN $expY $hh:$mm:$ss$tz";
290  
291   #Replace %LM by the content of the news.answer-pseudo-header Last-modified:
292   if ($LastModified) {
293     $$Subject =~ s/\%LM/$LastModified/;
294   } else {
295     $$Subject =~ s/[<\[{\(]?\%LM[>\]}\)]?//;
296   }
297
298   # Test mode?
299   if($Options{'t'} and $Options{'t'} !~ /console/i) {
300     $$NG = $Options{'t'};
301     $MID =~ s/@/-$Timestamp-test@/g;
302     $$ExtraHeaders .= "\n" if $$ExtraHeaders;
303     $$ExtraHeaders .= "X-Supersedes: $$Supersedes\nX-yapfaq-Remark: This is only a test message.";
304     undef $$Supersedes;
305   }
306
307   #Now create the complete Header:
308   push @Header, "From: $$From\n";
309   push @Header, "Newsgroups: $$NG\n";
310   push @Header, "Followup-To: $$Fup2\n" if $$Fup2;
311   push @Header, "Subject: $$Subject\n";
312   push @Header, "Message-ID: $MID\n";
313   push @Header, "Supersedes: $$Supersedes\n" if $$Supersedes;
314   push @Header, "Date: $date\n";
315   push @Header, "Expires: $expdate\n";
316   push @Header, "Sender: $$Sender\n" if $$Sender;
317   push @Header, "Mime-Version: 1.0\n";
318   push @Header, "Reply-To: $$ReplyTo\n" if $$ReplyTo;
319   push @Header, "Content-Type: text/plain; charset=ISO-8859-15\n";
320   push @Header, "Content-Transfer-Encoding: 8bit\n";
321   push @Header, "User-Agent: yapfaq/$VERSION\n";
322   if ($$ExtraHeaders) {
323     push @Header, "$_\n" for (split /\n/, $$ExtraHeaders);
324   }
325
326   my @Article = (@Header, "\n", @Body);
327
328   # post article
329   print "$$ActName: Posting article ...\n" if($Options{'v'});
330   my $failure = post(\@Article);
331   
332   if ($failure) {
333     print "$$ActName: Posting failed, ERROR.dat may have more information.\n" if($Options{'v'} && (!defined($Options{'t'}) || $Options{'t'} !~ /console/i));
334   } else {
335     updatestatus($ActName, $File, "$day.$month.$year", $MID) if !defined($Options{'t'});
336   }
337 }
338
339 ################################## post ##################################
340 # Takes a complete article (Header and Body).
341 #
342 # It opens a connection to $NNTPServer and posts the message.
343
344 sub post {
345   my ($ArticleR) = @_;
346   my ($failure) = -1;
347
348   # test mode - print article to console
349   if(defined($Options{'t'}) and $Options{'t'} =~ /console/i) {
350     print "-----BEGIN--------------------------------------------------\n";
351     print @$ArticleR;
352     print "------END---------------------------------------------------\n";
353   # pipe article to script
354   } elsif(defined($Options{'s'})) {
355     open (POST, "| $Options{'s'}") or die "$0: E: Cannot fork $Options{'s'}: $!\n";
356     print POST @$ArticleR;
357     close POST;
358     if ($? == 0) {
359       $failure = 0;
360     } else {
361       warn "$0: W: $Options{'s'} exited with status ", ($? >> 8), "\n";
362       $failure = $?;
363     }
364   # post article
365   } else {
366     my $NewsConnection = Net::NNTP->new($Config{'NNTPServer'}, Reader => 1) or die "$0: E: Can't connect to news server '$Config{'NNTPServer'}'!\n";
367     $NewsConnection->authinfo ($Config{'NNTPUser'}, $Config{'NNTPPass'}) if (defined($Config{'NNTPUser'}));
368     $NewsConnection->post();
369     $NewsConnection->datasend (@$ArticleR);
370     $NewsConnection->dataend();
371
372     if ($NewsConnection->ok()) {
373       $failure = 0;
374     # Posting failed? Save to ERROR.dat
375     } else {
376           warn "$0: W: Posting failed!\n";
377       open FH, ">>ERROR.dat";
378       print FH "\nPosting failed! Saving to ERROR.dat. Response from news server:\n";
379       print FH $NewsConnection->code();
380       print FH $NewsConnection->message();
381       print FH "\n";
382       print FH @$ArticleR;
383       print FH "-" x 80, "\n";
384       close FH;
385     }
386     $NewsConnection->quit();
387   }
388   return $failure;
389 }
390
391 __END__
392
393 ################################ Documentation #################################
394
395 =head1 NAME
396
397 yapfaq - Post Usenet FAQs I<(yet another postfaq)>
398
399 =head1 SYNOPSIS
400
401 B<yapfaq> [B<-Vhvpd>] [B<-t> I<newsgroups> | CONSOLE] [B<-f> I<project name>] [B<-s> I<program>] [B<-c> I<.rc file>]
402
403 =head1 REQUIREMENTS
404
405 =over 2
406
407 =item -
408
409 Perl 5.8 or later
410
411 =item -
412
413 Net::NNTP
414
415 =item -
416
417 Date::Calc
418
419 =item -
420
421 Getopt::Std
422
423 =back
424
425 Furthermore you need access to a news server to actually post FAQs.
426
427 =head1 DESCRIPTION
428
429 B<yapfaq> posts (one or more) FAQs to Usenet with a certain posting
430 frequency (every n days, weeks, months or years), adding all necessary
431 headers as defined in its config file (by default F<yapfaq.cfg>).
432
433 =head2 Configuration
434
435 F<yapfaq.cfg> consists of one or more blocks, separated by C<=====> on
436 a single line, each containing the configuration for one FAQ as a set
437 of definitions in the form of I<param = value>. Everything after a "#"
438 sign is ignored so you may comment your configuration file.
439
440 =over 4
441
442 =item B<Name> = I<project name>
443
444 A name referring to your FAQ, also used for generation of a Message-ID.
445
446 This value must be set.
447
448 =item B<File> = I<file name>
449
450 A file containing the message body of your FAQ and all pseudo headers
451 (subheaders in the news.answers style).
452
453 This value must be set.
454
455 =item B<Posting-frequency> = I<time period>
456
457 The posting frequency defines how often your FAQ will be posted.
458 B<yapfaq> will only post your FAQ if this period of time has passed
459 since the last posting.
460
461 You can declare that time period either in I<B<d>ays> or I<B<w>weeks>
462 or I<B<m>onths> or I<B<y>ears>.
463
464 This value must be set.
465
466 =item B<Expires> = I<time period> (optional)
467
468 The period of time after which your message will expire. An Expires
469 header will be calculated adding this time period to today's date.
470
471 You can declare this  time period either in I<B<d>ays> or I<B<w>weeks>
472 or I<B<m>onths> or I<B<y>ears>.
473
474 This setting is optional; the default is 3 months.
475
476 =item B<From> = I<author>
477
478 The author of your FAQ as it will appear in the From header of the
479 message.
480
481 This value must be set.
482
483 =item B<Subject> = I<subject>
484
485 The title of your FAQ as it will appear in the Subject header of the
486 message.
487
488 You may use the special string C<%LM> which will be replaced with
489 the contents of the Last-Modified subheader in your I<File>.
490
491 This value must be set.
492
493 =item B<NGs> = I<newsgroups>
494
495 A comma-separated list of newsgroup(s) to post your FAQ to as it will
496 appear in the Newsgroups header of the message.
497
498 This value must be set.
499
500 =item B<Fup2> = I<newsgroup | poster>  (optional)
501
502 A comma-separated list of newsgroup(s) or the special string I<poster>
503 as it will appear in the Followup-To header of the message.
504
505 This setting is optional.
506
507 =item B<MID-Format> = I<pattern>  (optional)
508
509 A pattern from which the message ID is generated as it will appear in
510 the Message-ID header of the message.
511
512 You may use the special strings C<%n> for the I<Name> of your project,
513 C<%d> for the date the message is posted, C<%m> for the month, C<%y>
514 for the year and C<%t> for a time stamp (number of seconds since the
515 epoch), respectively.
516
517 This setting is optional; the default is '<%n-%y-%m-%d@I<YOURHOST>>'
518 where I<YOURHOST> is the fully qualified domain name (FQDN) of the
519 host B<yapfaq> is running on. Obviously that will only work if you
520 have defined a reasonable hostname that the hostfqdn() function of
521 Net::Domain can return.
522
523 =item B<Supersede> = I<yes>  (optional)
524
525 Add Supersedes header to the message containing the Message-ID header
526 of the last posting.
527
528 This setting is optional; you should set it to yes or leave it out.
529
530 =item B<ExtraHeader> = I<additional headers>  (optional)
531
532 The contents of I<ExtraHeader> is added verbatim to the headers of
533 your message so you can add custom headers like Approved.
534
535 This setting is optional.
536
537 =back
538
539 =head3 Example configuration file
540
541     # name of your project
542     Name = 'testpost'
543     
544     # file to post (complete body and pseudo-headers)
545     # ($File.cfg contains data on last posting and last MID)
546     File = 'test.txt'
547     
548     # how often your project should be posted
549     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
550     Posting-frequency = '1d'
551     
552     # time period after which the posting should expire
553     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
554     # Expires = '3m'
555     
556     # header "From:"
557     From = 'test@domain.invalid'
558     
559     # header "Subject:"
560     # (may contain "%LM" which will be replaced by the contents of the
561     #  Last-Modified pseudo header).
562     Subject = 'test noreply ignore'
563     
564     # comma-separated list of newsgroup(s) to post to
565     # (header "Newsgroups:")
566     NGs = 'de.test'
567     
568     # header "Followup-To:"
569     # Fup2 = 'poster'
570     
571     # Message-ID ("%n" is $Name)
572     # MID-Format = '<%n-%d.%m.%y@domain.invalid>'
573     
574     # Supersede last posting?
575     Supersede = yes
576     
577     # extra headers (appended verbatim)
578     # use this for custom headers like "Approved:"
579     ExtraHeader = 'Approved: moderator@domain.invalid
580     X-Header: Some text'
581     
582     # other projects may follow separated with "====="
583     =====
584     
585     Name = 'othertest'
586     File = 'test.txt'
587     Posting-frequency = '2m'
588     From = 'My Name <my.name@domain.invalid>'
589     Subject = 'Test of yapfag <%LM>'
590     NGs = 'de.test,de.alt.test'
591     Fup2 = 'de.test'
592     MID-Format = '<%n-%m.%y@domain.invalid>'
593     Supersede = yes
594
595 =head3 Status Information
596
597 Information about the last post and about how to form message IDs for
598 posts is stored in a file named F<I<project name>.cfg> which will be
599 generated if it does not exist. Each of those status files will
600 contain two lines, the first being the date of the last time the FAQ
601 was posted and the second being the message ID of that incarnation.
602
603 =head2 Runtime Configuration
604
605 Apart from configuring which FAQ(s) to post you may (re)set some
606 runtime configuration variables via the .rcfile (by default
607 F<.yapfaqrc>). F<.yapfaqrc> must contain one definition in the form of
608 I<param = value> on each line; everything after a "#" sign is ignored.
609
610 If you omit some settings they will be set to default values hardcoded
611 in F<yapfaq.pl>.
612
613 B<Please note that all parameter names are case-sensitive!>
614
615 =over 4
616
617 =item B<NNTPServer> = I<NNTP server> (mandatory)
618
619 Host name of the NNTP server to post to. Must be set (or omitted; the
620 default is "localhost"); if set to en empty string, B<yapfaq> falls
621 back to Perl's build-in defaults (contents of environment variables
622 NNTPSERVER and NEWSHOST; if not set, default from Net::Config; if not
623 set, "news" is used).
624
625 =item B<NNTPUser> = I<user name> (optional)
626
627 User name used for authentication with the NNTP server (I<AUTHINFO
628 USER>).
629
630 This setting is optional; if it is not set, I<NNTPPass> is ignored and
631 no authentication is tried.
632
633 =item B<NNTPPass> = I<password> (optional)
634
635 Password used for authentication with the NNTP server (I<AUTHINFO
636 PASS>).
637
638 This setting is optional; it must be set if I<NNTPUser> is present.
639
640 =item B<Sender> = I<Sender header> (optional)
641
642 The Sender header that will be added to every posted message.
643
644 This setting is optional.
645
646 =item B<ConfigFile> = I<configuration file> (mandatory)
647
648 The configuration file defining the FAQ(s) to post. Must be set (or
649 omitted; the default is "yapfaq.cfg").
650
651 =item B<Program> = I<file name> (optional)
652
653 A program the article is piped to instead of posting it to Usenet.
654 See option "-f" below (which takes preference).
655
656 This setting is optional.
657
658 =back
659
660 =head3 Example runtime configuration file
661
662     NNTPServer = 'localhost'
663     NNTPUser   = ''
664     NNTPPass   = ''
665     Sender     = ''
666     ConfigFile = 'yapfaq.cfg'
667     Program    = ''
668
669 =head3 Using more than one runtime configuration
670
671 You may use more than one runtime configuration file with the B<-c>
672 option (see below).
673
674 =head1 OPTIONS
675
676 =over 3
677
678 =item B<-V> (version)
679
680 Print out version and copyright information on B<yapfaq> and exit.
681
682 =item B<-h> (help)
683
684 Print this man page and exit.
685
686 =item B<-v> (verbose)
687
688 Print out status information while running to STDOUT.
689
690 =item B<-p> (post unconditionally)
691
692 Post (all) FAQs unconditionally ignoring the posting frequency setting.
693
694 You may want to use this with the B<-f> option (see below).
695
696 =item B<-d> (dry run)
697
698 Start B<yapfaq> in simulation mode, i.e. don't post anything and don't
699 update any status information.
700
701 =item B<-t> I<newsgroup(s) | CONSOLE> (test)
702
703 Don't post to the newsgroups defined in F<yqpfaq.cfg>, but to the
704 (test) newsgroup(s) given after B<-t> as a comma-separated list or
705 print the FAQs to STDOUT separated by lines of dashes if the special
706 string C<CONSOLE> is given.  This can be used to preview what
707 B<yapfaq> would do without embarassing yourself on Usenet. 
708
709 The status files are not updated when this option is given.
710
711 When this option is used to post to some other newsgroup(s), a(nother)
712 timestamp is added to the Message-ID header and the Supersedes header
713 is replaced by a special X-Supersedes header.
714
715 You may want to use this with the B<-f> option (see below).
716
717 =item B<-f> I<project name>
718
719 Just deal with one FAQ only.
720
721 By default B<yapfaq> will work on all FAQs that are defined in
722 F<yapfaq.cfg>, check whether they are due for posting and - if they
723 are - post them. Consequently when the B<-p> option is set all FAQs
724 will be posted unconditionally. That may not be what you want to
725 achieve, so you can limit the operation of B<yapfaq> to the named FAQ
726 only.
727
728 =item B<-s> I<program> (pipe to script)
729
730 Instead of posting the article(s) to Usenet pipe them to the external
731 I<program> on STDIN (which may post the article(s) then). A return
732 value of 0 will be considered success.
733
734 For example, you may want to use the I<inews> utility from the INN package
735 or the much more powerful replacement I<tinews.pl> from
736 I<ftp://ftp.tin.org/tin/tools/tinews.pl> which is able to sign postings.
737
738 If I<Program> is also defined in the runtime configuration file (by default
739 F<.yapfaqrc>), B<-s> takes preference.
740
741 =item B<-c> I<.rc file>
742
743 Load another runtime configuration file (.rc file) than F<.yaofaq.rc>.
744
745 You may for example define another usenet server to post your FAQ(s)
746 to or load another configuration file defining (an)other FAQ(s).
747
748 =back
749
750 =head1 INSTALLATION
751
752 Just copy the contents of the tarball in some directory and get started.
753
754 You can post your first test with
755
756     yapfaq -c .yapfaqrc.sample
757
758 or copy F<.yapfaqrc.sample> to F<.yapfaqrc> and F<yapfaq.cfg.sample>
759 to F<yapfaq.cfg>, edit those files and get really started!
760
761 =back
762
763 =head1 EXAMPLES
764
765 Post all FAQs that are due for posting:
766
767     yapfaq
768
769 Do a dry run, showing which FAQs would be posted:
770
771     yapfaq -dv
772
773 Do a test run and print on STDOUT what the FAQ I<myfaq> would look
774 like when posted, regardless whether it is due for posting or not:
775
776     yapfaq -pt CONSOLE -f myfaq
777
778 Do a "real" test run and post the FAQ I<myfaq> to I<de.test>, but only
779 if it is due:
780
781     yapfaq -t de.test -f myfaq
782
783 Post all FAQs (that are due for posting) using inews from INN:
784
785     yapfaq -s inews
786
787 Do a dry run using a runtime configuration from .alternaterc, showing
788 which FAQs would be posted:
789
790     yapfaq -dvc .alternaterc
791
792 =head1 ENVIRONMENT
793
794 =over 4
795
796 =item NNTPSERVER
797
798 The default NNTP server to post to, used by the Net::NNTP module. You
799 can also  specify the server using the runtime configuration file (by
800 default F<.yapfaqrc>).
801
802 =back
803
804 =head1 FILES
805
806 =over 4
807
808 =item F<yapfaq.pl>
809
810 The script itself.
811
812 =item F<.yapfaqrc>
813
814 Runtime configuration file for B<yapfaq>.
815
816 =item F<yapfaq.cfg>
817
818 Configuration file for B<yapfaq>.
819
820 =item F<*.cfg>
821
822 Status data on FAQs.
823
824 The status files will be created on successful posting if they don't
825 already exist. The first line of the file will be the date of the last
826 time the FAQ was posted and the second line will be the message ID of
827 the last post of that FAQ.
828
829 =back
830
831 =head1 BUGS
832
833 Please report any bugs or feature request to the author or use the
834 bug tracker at L<http://bugs.th-h.de/>!
835
836 =head1 SEE ALSO
837
838 L<http://th-h.de/download/scripts.php> will have the current
839 version of this program.
840
841 This program is maintained using the Git version control system. You
842 may clone L<git://code.th-h.de/usenet/yapfaq.git> to check out the
843 current development tree or browse it on the web via
844 L<http://code.th-h.de/?p=usenet/yapfaq.git>.
845
846 =head1 AUTHOR
847
848 Thomas Hochstein <thh@inter.net>
849
850 Original author (up to version 0.5b, dating from 2003):
851 Marc Brockschmidt <marc@marcbrockschmidt.de>
852
853 =head1 COPYRIGHT AND LICENSE
854
855 Copyright (c) 2003 Marc Brockschmidt <marc@marcbrockschmidt.de>
856
857 Copyright (c) 2010 Thomas Hochstein <thh@inter.net>
858
859 This program is free software; you may redistribute it and/or modify it
860 under the same terms as Perl itself.
861
862 =cut
This page took 0.034375 seconds and 3 git commands to generate.