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