"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/tools/tinews.pl" (21 Dec 2016, 38512 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "tinews.pl": 2.4.0_vs_2.4.1.

    1 #! /usr/bin/perl -w
    2 #
    3 # reads an article on STDIN, mails any copies if requied,
    4 # signs the article and posts it.
    5 #
    6 #
    7 # Copyright (c) 2002-2017 Urs Janssen <urs@tin.org>,
    8 #                         Marc Brockschmidt <marc@marcbrockschmidt.de>
    9 #
   10 # Redistribution and use in source and binary forms, with or without
   11 # modification, are permitted provided that the following conditions
   12 # are met:
   13 # 1. Redistributions of source code must retain the above copyright
   14 #    notice, this list of conditions and the following disclaimer.
   15 # 2. Redistributions in binary form must reproduce the above copyright
   16 #    notice, this list of conditions and the following disclaimer in the
   17 #    documentation and/or other materials provided with the distribution.
   18 # 3. The name of the author may not be used to endorse or promote
   19 #    products derived from this software without specific prior written
   20 #    permission.
   21 #
   22 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
   23 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   24 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   25 # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
   26 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   27 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   28 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   29 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
   30 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
   31 # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32 # POSSIBILITY OF SUCH DAMAGE.
   33 #
   34 #
   35 # TODO: - add debug mode which doesn't delete tmp-files and is verbose
   36 #       - add pid to pgptmpf to allow multiple simultaneous instances
   37 #       - check for /etc/nntpserver (and /etc/news/server)
   38 #       - add $NEWSHOST fallback for $NNTPSERVER (like in Net::NNTP)?
   39 #       - add $PGPOPTS, $PGPPATH and $GNUPGHOME support
   40 #       - cleanup and remove duplicated code
   41 #       - option to convert CRLF to LF in input
   42 #       - use STARTTLS (if Net::NNTP is recent enough and server supports it)
   43 #       - quote inpupt properly before passing to shell
   44 #
   45 
   46 use strict;
   47 use warnings;
   48 
   49 # version Number
   50 my $version = "1.1.42";
   51 
   52 my %config;
   53 
   54 # configuration, may be overwritten via ~/.tinewsrc
   55 $config{'NNTPServer'}   = 'news';   # your NNTP servers name, may be set via $NNTPSERVER
   56 $config{'NNTPPort'}     = 119;  # NNTP-port, may be set via $NNTPPORT
   57 $config{'NNTPUser'}     = '';   # username for nntp-auth, may be set via ~/.newsauth or ~/.nntpauth
   58 $config{'NNTPPass'}     = '';   # password for nntp-auth, may be set via ~/.newsauth or ~/.nntpauth
   59 
   60 $config{'PGPSigner'}    = '';   # sign as who?
   61 $config{'PGPPass'}      = '';   # pgp2 only
   62 $config{'PathtoPGPPass'}= '';   # pgp2, pgp5, pgp6 and gpg
   63 $config{'PGPPassFD'}    = 9;    # file descriptor used for input redirection of PathtoPGPPass; GPG1, GPG2, PGP5 and PGP6 only
   64 
   65 $config{'pgp'}          = '/usr/bin/pgp';   # path to pgp
   66 $config{'PGPVersion'}   = '2';  # Use 2 for 2.X, 5 for PGP5, 6 for PGP6, GPG or GPG1 for GPG1 and GPG2 for GPG2
   67 $config{'digest-algo'}  = 'MD5';# Digest Algorithm for GPG. Must be supported by your installation
   68 
   69 $config{'Interactive'}  = 'yes';# allow interactive usage
   70 
   71 $config{'sig_path'}     = glob('~/.signature'); # path to signature
   72 $config{'add_signature'}= 'yes';# Add $config{'sig_path'} to posting if there is no sig
   73 $config{'sig_max_lines'}= 4;    # max number of signatures lines
   74 
   75 $config{'sendmail'}     = '| /usr/sbin/sendmail -i -t'; # set to '' to disable mail-actions
   76 
   77 $config{'pgptmpf'}      = 'pgptmp'; # temporary file for PGP.
   78 
   79 $config{'pgpheader'}    = 'X-PGP-Sig';
   80 $config{'pgpbegin'}     = '-----BEGIN PGP SIGNATURE-----';  # Begin of PGP-Signature
   81 $config{'pgpend'}       = '-----END PGP SIGNATURE-----';    # End of PGP-Signature
   82 
   83 # $config{'canlock_secret'} = '~/.cancelsecret';        # Path to canlock secret file
   84 
   85 # $config{'ignore_headers'} = '';      # headers to be ignored during signing
   86 
   87 $config{'PGPSignHeaders'} = ['From', 'Newsgroups', 'Subject', 'Control',
   88     'Supersedes', 'Followup-To', 'Date', 'Injection-Date', 'Sender', 'Approved',
   89     'Message-ID', 'Reply-To', 'Cancel-Key', 'Also-Control',
   90     'Distribution'];
   91 $config{'PGPorderheaders'} = ['from', 'newsgroups', 'subject', 'control',
   92     'supersedes', 'followup-To', 'date', 'injection-date', 'organization',
   93     'lines', 'sender', 'approved', 'distribution', 'message-id',
   94     'references', 'reply-to', 'mime-version', 'content-type',
   95     'content-transfer-encoding', 'summary', 'keywords', 'cancel-lock',
   96     'cancel-key', 'also-control', 'x-pgp', 'user-agent'];
   97 
   98 ################################################################################
   99 
  100 use Getopt::Long qw(GetOptions);
  101 use Net::NNTP;
  102 use Time::Local;
  103 use Term::ReadLine;
  104 
  105 (my $pname = $0) =~ s#^.*/##;
  106 
  107 # read config file (first match counts) from
  108 # $XDG_CONFIG_HOME/tinewsrc ~/.config/tinewsrc ~/.tinewsrc
  109 # if present
  110 my $TINEWSRC = undef;
  111 my (@try, %seen);
  112 if ($ENV{'XDG_CONFIG_HOME'}) {
  113     push(@try, (glob("$ENV{'XDG_CONFIG_HOME'}/tinewsrc"))[0]);
  114 }
  115 push(@try, (glob('~/.config/tinewsrc'))[0], (glob('~/.tinewsrc'))[0]);
  116 
  117 foreach (grep { ! $seen{$_}++ } @try) {
  118     last if (open($TINEWSRC, '<', $_));
  119     $TINEWSRC = undef;
  120 }
  121 if (defined($TINEWSRC)) {
  122     while (defined($_ = <$TINEWSRC>)) {
  123         if (m/^([^#\s=]+)\s*=\s*(\S[^#]+)/io) {
  124             chomp($config{$1} = $2);
  125         }
  126     }
  127     close($TINEWSRC);
  128 }
  129 
  130 # digest-algo is case sensitive and should be all uppercase
  131 $config{'digest-algo'} = uc($config{'digest-algo'});
  132 
  133 # these env-vars have higher priority
  134 $config{'NNTPServer'} = $ENV{'NNTPSERVER'} if ($ENV{'NNTPSERVER'});
  135 $config{'NNTPPort'} = $ENV{'NNTPPORT'} if ($ENV{'NNTPPORT'});
  136 
  137 # Get options:
  138 $Getopt::Long::ignorecase=0;
  139 $Getopt::Long::bundling=1;
  140 GetOptions('A|V|W|O|no-organization|h|headers' => [], # do nothing
  141     'debug|D|N' => \$config{'debug'},
  142     'port|p=i'  => \$config{'NNTPPort'},
  143     'no-sign|X' => \$config{'no_sign'},
  144     'no-control|R'  => \$config{'no_control'},
  145     'no-signature|S'    => \$config{'no_signature'},
  146     'no-canlock|L'  => \$config{'no_canlock'},
  147     'no-injection-date|I'   => \$config{'no-injection-date'},
  148     'force-auth|Y'  => \$config{'force_auth'},
  149     'approved|a=s'  => \$config{'approved'},
  150     'control|c=s'   => \$config{'control'},
  151     'distribution|d=s'  => \$config{'distribution'},
  152     'expires|e=s'   => \$config{'expires'},
  153     'from|f=s'  => \$config{'from'},
  154     'ignore-headers|i=s'    => \$config{'ignore_headers'},
  155     'followupto|w=s'    => \$config{'followup-to'},
  156     'newsgroups|n=s'    => \$config{'newsgroups'},
  157     'replyto|r=s'   => \$config{'reply-to'},
  158     'savedir|s=s'   => \$config{'savedir'},
  159     'subject|t=s'   => \$config{'subject'},
  160     'references|F=s'    => \$config{'references'},
  161     'organization|o=s'  => \$config{'organization'},
  162     'path|x=s'  => \$config{'path'},
  163     'help|H'    => \$config{'help'},
  164     'version|v' => \$config{'version'}
  165 );
  166 
  167 foreach (@ARGV) {
  168     print STDERR "Unknown argument $_.";
  169     usage();
  170 }
  171 
  172 if ($config{'version'}) {
  173     version();
  174     exit 0;
  175 }
  176 
  177 usage() if ($config{'help'});
  178 
  179 my $sha_mod=undef;
  180 # Cancel-Locks require some more modules
  181 if ($config{'canlock_secret'} && !$config{'no_canlock'}) {
  182     foreach ('Digest::SHA qw(sha1)', 'Digest::SHA1()') {
  183         eval "use $_";
  184         if (!$@) {
  185             ($sha_mod = $_) =~ s#( qw\(sha1\)|\(\))##;
  186             last;
  187         }
  188     }
  189     foreach ('MIME::Base64()', 'Digest::HMAC_SHA1()') {
  190         eval "use $_";
  191         if ($@ || !defined($sha_mod)) {
  192             $config{'no_canlock'} = 1;
  193             warn "Cancel-Locks disabled: Can't locate ".$_."\n" if $config{'debug'};
  194             last;
  195         }
  196     }
  197 }
  198 
  199 my $term = Term::ReadLine->new('tinews');
  200 my $attribs = $term->Attribs;
  201 my $in_header = 1;
  202 my (%Header, @Body, $PGPCommand);
  203 
  204 if (! $config{'no_sign'}) {
  205     $config{'PGPSigner'} = $ENV{'SIGNER'} if ($ENV{'SIGNER'});
  206     $config{'PathtoPGPPass'} = $ENV{'PGPPASSFILE'} if ($ENV{'PGPPASSFILE'});
  207     if ($config{'PathtoPGPPass'}) {
  208         open(my $PGPPass, '<', (glob($config{'PathtoPGPPass'}))[0]) or
  209             $config{'Interactive'} && die("$0: Can't open ".$config{'PathtoPGPPass'}.": $!");
  210         chomp($config{'PGPPass'} = <$PGPPass>);
  211         close($PGPPass);
  212     }
  213     if ($config{'PGPVersion'} eq '2' && $ENV{'PGPPASS'}) {
  214         $config{'PGPPass'} = $ENV{'PGPPASS'};
  215     }
  216 }
  217 
  218 # Remove unwanted headers from PGPSignHeaders
  219 if (${config{'ignore_headers'}}) {
  220     my @hdr_to_ignore = split(/,/, ${config{'ignore_headers'}});
  221     foreach my $hdr (@hdr_to_ignore) {
  222         @{$config{'PGPSignHeaders'}} = map {lc($_) eq lc($hdr) ? () : $_} @{$config{'PGPSignHeaders'}};
  223     }
  224 }
  225 # Read the message and split the header
  226 readarticle(\%Header, \@Body);
  227 
  228 # Add signature if there is none
  229 if (!$config{'no_signature'}) {
  230     if ($config{'add_signature'} && !grep {/^-- /} @Body) {
  231         if (-r $config{'sig_path'}) {
  232             my $l = 0;
  233             push @Body, "-- \n";
  234             open(my $SIGNATURE, '<', $config{'sig_path'}) or die("Can't open " . $config{'sig_path'} . ": $!");
  235             while (<$SIGNATURE>){
  236                 die $config{'sig_path'} . " longer than " . $config{'sig_max_lines'}. " lines!" if (++$l > $config{'sig_max_lines'});
  237                 push @Body, $_;
  238             }
  239             close($SIGNATURE);
  240         } else {
  241             if ($config{'debug'}) {
  242                 warn "Tried to add " . $config{'sig_path'} . ", but it is unreadable";
  243             }
  244         }
  245     }
  246 }
  247 
  248 # import headers set in the environment
  249 if (!defined($Header{'reply-to'})) {
  250     if ($ENV{'REPLYTO'}) {
  251         chomp($Header{'reply-to'} = "Reply-To: " . $ENV{'REPLYTO'});
  252         $Header{'reply-to'} .= "\n";
  253     }
  254 }
  255 foreach ('DISTRIBUTION', 'ORGANIZATION') {
  256     if (!defined($Header{lc($_)}) && $ENV{$_}) {
  257         chomp($Header{lc($_)} = ucfirst($_).": " . $ENV{$_});
  258         $Header{lc($_)} .= "\n";
  259     }
  260 }
  261 
  262 # overwrite headers if specified via cmd-line
  263 foreach ('Approved', 'Control', 'Distribution', 'Expires',
  264     'From', 'Followup-To', 'Newsgroups',' Reply-To', 'Subject',
  265     'References', 'Organization', 'Path') {
  266     next if (!defined($config{lc($_)}));
  267     chomp($Header{lc($_)} = $_ . ": " . $config{lc($_)});
  268     $Header{lc($_)} .= "\n";
  269 }
  270 
  271 # verify/add/remove headers
  272 foreach ('From', 'Subject') {
  273     die("$0: No $_:-header defined.") if (!defined($Header{lc($_)}));
  274 }
  275 
  276 $Header{'date'} = "Date: ".getdate()."\n" if (!defined($Header{'date'}) || $Header{'date'} !~ m/^[^\s:]+: .+/o);
  277 $Header{'injection-date'} = "Injection-Date: ".getdate()."\n" if (!$config{'no-injection-date'});
  278 
  279 if (defined($Header{'user-agent'})) {
  280     chomp $Header{'user-agent'};
  281     $Header{'user-agent'} = $Header{'user-agent'}." ".$pname."/".$version."\n";
  282 }
  283 
  284 delete $Header{'x-pgp-key'} if (!$config{'no_sign'} && defined($Header{'x-pgp-key'}));
  285 
  286 
  287 # No control messages allowed when using -R|--no-control
  288 if ($config{'no_control'} and $Header{control}) {
  289     print STDERR "No control messages allowed.\n";
  290     exit 1;
  291 }
  292 
  293 # various checks
  294 if ($config{'debug'}) {
  295     foreach (keys %Header) {
  296         warn "Raw 8-bit data in the following header:\n$Header{$_}" if ($Header{$_} =~ m/[\x80-\xff]/o);
  297     }
  298     if (!defined($Header{'mime-version'}) || !defined($Header{'content-type'}) || !defined($Header{'content-transfer-encoding'})) {
  299         warn "8bit body without MIME-headers\n" if (grep {/[\x80-\xff]/} @Body);
  300     }
  301 }
  302 
  303 # try ~/.newsauth if no $config{'NNTPPass'} was set
  304 if (!$config{'NNTPPass'}) {
  305     my ($l, $server, $pass, $user);
  306     if (-r (glob("~/.newsauth"))[0]) {
  307         open (my $NEWSAUTH, '<', (glob("~/.newsauth"))[0]) or die("Can't open ~/.newsauth: $!");
  308         while ($l = <$NEWSAUTH>) {
  309             chomp $l;
  310             next if ($l =~ m/^[#\s]/);
  311             ($server, $pass, $user) = split(/\s+\b/, $l);
  312             last if ($server =~ m/\Q$config{'NNTPServer'}\E/);
  313         }
  314         close($NEWSAUTH);
  315         if ($pass && $server =~ m/\Q$config{'NNTPServer'}\E/) {
  316             $config{'NNTPPass'} = $pass;
  317             $config{'NNTPUser'} = $user || getlogin || getpwuid($<) || $ENV{USER};
  318         } else {
  319             $pass = $user = "";
  320         }
  321     }
  322     # try ~/.nntpauth if we still got no password
  323     if (!$pass) {
  324         if (-r (glob("~/.nntpauth"))[0]) {
  325             open (my $NNTPAUTH, '<', (glob("~/.nntpauth"))[0]) or die("Can't open ~/.nntpauth: $!");
  326             while ($l = <$NNTPAUTH>) {
  327                 chomp $l;
  328                 next if ($l =~ m/^[#\s]/);
  329                 ($server, $user, $pass) = split(/\s+\b/, $l);
  330                 last if ($server =~ m/\Q$config{'NNTPServer'}\E/);
  331             }
  332             close($NNTPAUTH);
  333             if ($pass && $server =~ m/\Q$config{'NNTPServer'}\E/) {
  334                 $config{'NNTPPass'} = $pass;
  335                 $config{'NNTPUser'} = $user || getlogin || getpwuid($<) || $ENV{USER};
  336             }
  337         }
  338     }
  339 }
  340 
  341 if (! $config{'savedir'} && defined($Header{'newsgroups'}) && !defined($Header{'message-id'})) {
  342     my $Server = AuthonNNTP();
  343     my $ServerMsg = $Server->message();
  344     $Server->datasend('.');
  345     $Server->dataend();
  346     $Server->quit();
  347     $Header{'message-id'} = "Message-ID: $1\n" if ($ServerMsg =~ m/(<\S+\@\S+>)/o);
  348 }
  349 
  350 if (!defined($Header{'message-id'})) {
  351     my $hname;
  352     eval "use Sys::Hostname";
  353     if ($@) {
  354         chomp($hname = `hostname`);
  355     } else {
  356         $hname = hostname();
  357     }
  358     my ($hostname,) = gethostbyname($hname);
  359     if (defined($hostname) && $hostname =~ m/\./io) {
  360         $Header{'message-id'} = "Message-ID: " . sprintf("<N%xI%xT%x@%s>\n", $>, timelocal(localtime), $$, $hostname);
  361     }
  362 }
  363 
  364 # add Cancel-Lock (and Cancel-Key) header(s) if requested
  365 if ($config{'canlock_secret'} && !$config{'no_canlock'} && defined($Header{'message-id'})) {
  366     open(my $CANLock, '<', (glob($config{'canlock_secret'}))[0]) or die("$0: Can't open " . $config{'canlock_secret'} . ": $!");
  367     chomp(my $key = <$CANLock>);
  368     close($CANLock);
  369     (my $data = $Header{'message-id'}) =~ s#^Message-ID: ##i;
  370     chomp $data;
  371     my $digest = Digest::HMAC_SHA1::hmac_sha1($data, $key);
  372     my $cancel_key = MIME::Base64::encode($digest, '');
  373     my $cancel_lock;
  374     if ($sha_mod =~ m/SHA1/) {
  375         $cancel_lock = MIME::Base64::encode(Digest::SHA1::sha1($cancel_key, ''));
  376     } else {
  377         $cancel_lock = MIME::Base64::encode(Digest::SHA::sha1($cancel_key, ''));
  378     }
  379     if (defined($Header{'cancel-lock'})) {
  380         chomp $Header{'cancel-lock'};
  381         $Header{'cancel-lock'} .= " sha1:" . $cancel_lock;
  382     } else {
  383         $Header{'cancel-lock'} = "Cancel-Lock: sha1:" . $cancel_lock;
  384     }
  385 
  386     if ((defined($Header{'supersedes'}) && $Header{'supersedes'} =~ m/^Supersedes:\s+<\S+>\s*$/i) || (defined($Header{'control'}) && $Header{'control'} =~ m/^Control:\s+cancel\s+<\S+>\s*$/i) ||(defined($Header{'also-control'}) && $Header{'also-control'} =~ m/^Also-Control:\s+cancel\s+<\S+>\s*$/i)) {
  387         if (defined($Header{'also-control'}) && $Header{'also-control'} =~ m/^Also-Control:\s+cancel\s+/i) {
  388             ($data = $Header{'also-control'}) =~ s#^Also-Control:\s+cancel\s+##i;
  389             chomp $data;
  390             $cancel_key = MIME::Base64::encode(Digest::HMAC_SHA1::hmac_sha1($data, $key),'');
  391         } else {
  392             if (defined($Header{'control'}) && $Header{'control'} =~ m/^Control: cancel /i) {
  393                 ($data = $Header{'control'})=~ s#^Control:\s+cancel\s+##i;
  394                 chomp $data;
  395                 $cancel_key = MIME::Base64::encode(Digest::HMAC_SHA1::hmac_sha1($data, $key),'');
  396             } else {
  397                 if (defined($Header{'supersedes'})) {
  398                     ($data = $Header{'supersedes'}) =~ s#^Supersedes: ##i;
  399                     chomp $data;
  400                     $cancel_key = MIME::Base64::encode(Digest::HMAC_SHA1::hmac_sha1($data, $key),'');
  401                 }
  402             }
  403         }
  404         if (defined($Header{'cancel-key'})) {
  405             chomp $Header{'cancel-key'};
  406             $Header{'cancel-key'} .= " sha1:" . $cancel_key . "\n";
  407         } else {
  408             $Header{'cancel-key'} = "Cancel-Key: sha1:" . $cancel_key . "\n";
  409         }
  410     }
  411 }
  412 
  413 # set Posted-And-Mailed if we send a mailcopy to someone else
  414 if ($config{'sendmail'} && defined($Header{'newsgroups'}) && (defined($Header{'to'}) || defined($Header{'cc'}) || defined($Header{'bcc'}))) {
  415     foreach ('to', 'bcc', 'cc') {
  416         if (defined($Header{$_}) && $Header{$_} ne $Header{'from'}) {
  417             $Header{'posted-and-mailed'} = "Posted-And-Mailed: yes\n";
  418             last;
  419         }
  420     }
  421 }
  422 
  423 if (! $config{'no_sign'}) {
  424     if (!$config{'PGPSigner'}) {
  425         chomp($config{'PGPSigner'} = $Header{'from'});
  426         $config{'PGPSigner'} =~ s/^[^\s:]+: (.*)/$1/;
  427     }
  428     $PGPCommand = getpgpcommand($config{'PGPVersion'});
  429 }
  430 
  431 # (re)move mail-headers
  432 my ($To, $Cc, $Bcc, $Newsgroups) = '';
  433 $To = $Header{'to'} if (defined($Header{'to'}));
  434 $Cc = $Header{'cc'} if (defined($Header{'cc'}));
  435 $Bcc = $Header{'bcc'} if (defined($Header{'bcc'}));
  436 delete $Header{$_} foreach ('to', 'cc', 'bcc');
  437 $Newsgroups = $Header{'newsgroups'} if (defined($Header{'newsgroups'}));
  438 
  439 my $MessageR = [];
  440 
  441 if ($config{'no_sign'}) {
  442     # don't sign article
  443     push @$MessageR, $Header{$_} for (keys %Header);
  444     push @$MessageR, "\n", @Body;
  445 } else {
  446     # sign article
  447     $MessageR = signarticle(\%Header, \@Body);
  448 }
  449 
  450 # post or save article
  451 if (! $config{'savedir'}) {
  452     postarticle($MessageR) if ($Newsgroups);
  453 } else {
  454     savearticle($MessageR) if ($Newsgroups);
  455 }
  456 
  457 # mail article
  458 if (($To || $Cc || $Bcc) && $config{'sendmail'}) {
  459     open(my $MAIL, $config{'sendmail'}) || die("$!");
  460     unshift @$MessageR, "$To" if ($To);
  461     unshift @$MessageR, "$Cc" if ($Cc);
  462     unshift @$MessageR, "$Bcc" if ($Bcc);
  463     print($MAIL @$MessageR);
  464     close($MAIL);
  465 }
  466 
  467 # Game over. Insert new coin.
  468 exit;
  469 
  470 
  471 #-------- sub readarticle
  472 #
  473 sub readarticle {
  474     my ($HeaderR, $BodyR) = @_;
  475     my $currentheader;
  476     while (defined($_ = <>)) {
  477         if ($in_header) {
  478             if (m/^$/o) { #end of header
  479                 $in_header = 0;
  480             } elsif (m/^([^\s:]+): (.*)$/s) {
  481                 $currentheader = lc($1);
  482                 $$HeaderR{$currentheader} = "$1: $2";
  483             } elsif (m/^[ \t]/o) {
  484                 $$HeaderR{$currentheader} .= $_;
  485 #           } elsif (m/^([^\s:]+):$/) { # skip over empty headers
  486 #               next;
  487             } else {
  488                 chomp($_);
  489                 # TODO: quote esc. sequences?
  490                 die("'$_' is not a correct header-line");
  491             }
  492         } else {
  493             push @$BodyR, $_;
  494         }
  495     }
  496     return;
  497 }
  498 
  499 #-------- sub getdate
  500 # getdate generates a date and returns it.
  501 #
  502 sub getdate {
  503     my @time = localtime;
  504     my $ss = ($time[0]<10) ? "0".$time[0] : $time[0];
  505     my $mm = ($time[1]<10) ? "0".$time[1] : $time[1];
  506     my $hh = ($time[2]<10) ? "0".$time[2] : $time[2];
  507     my $day = $time[3];
  508     my $month = ($time[4]+1 < 10) ? "0".($time[4]+1) : $time[4]+1;
  509     my $monthN = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$time[4]];
  510     my $wday = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat")[$time[6]];
  511     my $year = $time[5] + 1900;
  512     my $offset = timelocal(localtime) - timelocal(gmtime);
  513     my $sign ="+";
  514     if ($offset < 0) {
  515         $sign ="-";
  516         $offset *= -1;
  517     }
  518     my $offseth = int($offset/3600);
  519     my $offsetm = int(($offset - $offseth*3600)/60);
  520     my $tz = sprintf ("%s%0.2d%0.2d", $sign, $offseth, $offsetm);
  521     return "$wday, $day $monthN $year $hh:$mm:$ss $tz";
  522 }
  523 
  524 
  525 #-------- sub AuthonNNTP
  526 # AuthonNNTP opens the connection to a Server and returns a Net::NNTP-Object.
  527 #
  528 # User, Password and Server are defined before as elements
  529 # of the global hash %config. If no values for user or password
  530 # are defined, the sub will try to ask the user (only if
  531 # $config{'Interactive'} is != 0).
  532 sub AuthonNNTP {
  533     my $Server = Net::NNTP->new($config{'NNTPServer'}, Reader => 1, Debug => 0, Port => $config{'NNTPPort'})
  534         or die("$0: Can't connect to ".$config{'NNTPServer'}.":".$config{'NNTPPort'}."!\n");
  535     my $ServerMsg = "";
  536     my $ServerCod = $Server->code();
  537 
  538     # no read and/or write access - give up
  539     if ($ServerCod < 200 || $ServerCod > 201) {
  540         $ServerMsg = $Server->message();
  541         $Server->quit();
  542         die($0.": ".$ServerCod." ".$ServerMsg."\n");
  543     }
  544 
  545     # read access - try auth
  546     if ($ServerCod == 201 || $config{'force_auth'}) {
  547         if ($config{'NNTPPass'} eq "") {
  548             if ($config{'Interactive'}) {
  549                 $config{'NNTPUser'} = $term->readline("Your Username at ".$config{'NNTPServer'}.": ");
  550                 $attribs->{redisplay_function} = $attribs->{shadow_redisplay};
  551                 $config{'NNTPPass'} = $term->readline("Password for ".$config{'NNTPUser'}." at ".$config{'NNTPServer'}.": ");
  552             } else {
  553                 $ServerMsg = $Server->message();
  554                 $Server->quit();
  555                 die($0.": ".$ServerCod." ".$ServerMsg."\n");
  556             }
  557         }
  558         $Server->authinfo($config{'NNTPUser'}, $config{'NNTPPass'});
  559         $ServerCod = $Server->code();
  560         $ServerMsg = $Server->message();
  561         if ($ServerCod != 281) { # auth failed
  562             $Server->quit();
  563             die $0.": ".$ServerCod." ".$ServerMsg."\n";
  564         }
  565     }
  566 
  567     $Server->post();
  568     $ServerCod = $Server->code();
  569     if ($ServerCod == 480) {
  570         if ($config{'NNTPPass'} eq "") {
  571             if ($config{'Interactive'}) {
  572                 $config{'NNTPUser'} = $term->readline("Your Username at ".$config{'NNTPServer'}.": ");
  573                 $attribs->{redisplay_function} = $attribs->{shadow_redisplay};
  574                 $config{'NNTPPass'} = $term->readline("Password for ".$config{'NNTPUser'}." at ".$config{'NNTPServer'}.": ");
  575             } else {
  576                 $ServerMsg = $Server->message();
  577                 $Server->quit();
  578                 die($0.": ".$ServerCod." ".$ServerMsg."\n");
  579             }
  580         }
  581         $Server->authinfo($config{'NNTPUser'}, $config{'NNTPPass'});
  582         $Server->post();
  583     }
  584     return $Server;
  585 }
  586 
  587 
  588 #-------- sub getpgpcommand
  589 # getpgpcommand generates the command to sign the message and returns it.
  590 #
  591 # Receives:
  592 #   - $PGPVersion: A scalar holding the PGPVersion
  593 sub getpgpcommand {
  594     my ($PGPVersion) = @_;
  595     my $found = 0;
  596 
  597     if ($config{'pgp'} !~ /^\//) {
  598         foreach(split(/:/, $ENV{'PATH'})) {
  599             if (-x $_."/".$config{'pgp'}) {
  600                 $found++;
  601                 last;
  602             }
  603         }
  604     }
  605     if (!-x $config{'pgp'} && ! $found) {
  606         warn "PGP signing disabled: Can't locate executable ".$config{'pgp'}."\n" if $config{'debug'};
  607         $config{'no_sign'} = 1;
  608     }
  609 
  610     if ($PGPVersion eq '2') {
  611         if ($config{'PGPPass'}) {
  612             $PGPCommand = "PGPPASS=\"".$config{'PGPPass'}."\" ".$config{'pgp'}." -z -u \"".$config{'PGPSigner'}."\" +verbose=0 language='en' -saft <".$config{'pgptmpf'}.".txt >".$config{'pgptmpf'}.".txt.asc";
  613         } elsif ($config{'Interactive'}) {
  614             $PGPCommand = $config{'pgp'}." -z -u \"".$config{'PGPSigner'}."\" +verbose=0 language='en' -saft <".$config{'pgptmpf'}.".txt >".$config{'pgptmpf'}.".txt.asc";
  615         } else {
  616             die("$0: Passphrase is unknown!\n");
  617         }
  618     } elsif ($PGPVersion eq '5') {
  619         if ($config{'PathtoPGPPass'}) {
  620             $PGPCommand = "PGPPASSFD=".$config{'PGPPassFD'}." ".$config{'pgp'}."s -u \"".$config{'PGPSigner'}."\" -t --armor -o ".$config{'pgptmpf'}.".txt.asc -z -f < ".$config{'pgptmpf'}.".txt ".$config{'PGPPassFD'}."<".$config{'PathtoPGPPass'};
  621         } elsif ($config{'Interactive'}) {
  622             $PGPCommand = $config{'pgp'}."s -u \"".$config{'PGPSigner'}."\" -t --armor -o ".$config{'pgptmpf'}.".txt.asc -z -f < ".$config{'pgptmpf'}.".txt";
  623         } else {
  624             die("$0: Passphrase is unknown!\n");
  625         }
  626     } elsif ($PGPVersion eq '6') { # this is untested
  627         if ($config{'PathtoPGPPass'}) {
  628             $PGPCommand = "PGPPASSFD=".$config{'PGPPassFD'}." ".$config{'pgp'}." -u \"".$config{'PGPSigner'}."\" -saft -o ".$config{'pgptmpf'}.".txt.asc < ".$config{'pgptmpf'}.".txt ".$config{'PGPPassFD'}."<".$config{'PathtoPGPPass'};
  629         } elsif ($config{'Interactive'}) {
  630             $PGPCommand = $config{'pgp'}." -u \"".$config{'PGPSigner'}."\" -saft -o ".$config{'pgptmpf'}.".txt.asc < ".$config{'pgptmpf'}.".txt";
  631         } else {
  632             die("$0: Passphrase is unknown!\n");
  633         }
  634     } elsif ($PGPVersion =~ m/GPG1?$/io) {
  635         if ($config{'PathtoPGPPass'}) {
  636             $PGPCommand = $config{'pgp'}." --emit-version --digest-algo $config{'digest-algo'} -a -u \"".$config{'PGPSigner'}."\" -o ".$config{'pgptmpf'}.".txt.asc --no-tty --batch --passphrase-fd ".$config{'PGPPassFD'}." ".$config{'PGPPassFD'}."<".$config{'PathtoPGPPass'}." --clearsign ".$config{'pgptmpf'}.".txt";
  637         } elsif ($config{'Interactive'}) {
  638             $PGPCommand = $config{'pgp'}." --emit-version --digest-algo $config{'digest-algo'} -a -u \"".$config{'PGPSigner'}."\" -o ".$config{'pgptmpf'}.".txt.asc --no-secmem-warning --no-batch --clearsign ".$config{'pgptmpf'}.".txt";
  639         } else {
  640             die("$0: Passphrase is unknown!\n");
  641         }
  642     } elsif ($PGPVersion =~ m/GPG2$/io) {
  643         if ($config{'PathtoPGPPass'}) {
  644             $PGPCommand = $config{'pgp'}." --pinentry-mode loopback --emit-version --digest-algo $config{'digest-algo'} -a -u \"".$config{'PGPSigner'}."\" -o ".$config{'pgptmpf'}.".txt.asc --no-tty --batch --passphrase-fd ".$config{'PGPPassFD'}." ".$config{'PGPPassFD'}."<".$config{'PathtoPGPPass'}." --clearsign ".$config{'pgptmpf'}.".txt";
  645         } elsif ($config{'Interactive'}) {
  646             $PGPCommand = $config{'pgp'}." --emit-version --digest-algo $config{'digest-algo'} -a -u \"".$config{'PGPSigner'}."\" -o ".$config{'pgptmpf'}.".txt.asc --no-secmem-warning --no-batch --clearsign ".$config{'pgptmpf'}.".txt";
  647         } else {
  648             die("$0: Passphrase is unknown!\n");
  649         }
  650     } else {
  651         die("$0: Unknown PGP-Version $PGPVersion!");
  652     }
  653     return $PGPCommand;
  654 }
  655 
  656 
  657 #-------- sub postarticle
  658 # postarticle posts your article to your Newsserver.
  659 #
  660 # Receives:
  661 #   - $ArticleR: A reference to an array containing the article
  662 sub postarticle {
  663     my ($ArticleR) = @_;
  664 
  665     my $Server = AuthonNNTP();
  666     my $ServerCod = $Server->code();
  667     if ($ServerCod == 340) {
  668         $Server->datasend(@$ArticleR);
  669         $Server->dataend();
  670         if (!$Server->ok()) {
  671             my $ServerMsg = $Server->message();
  672             $Server->quit();
  673             die("\n$0: Posting failed! Response from news server:\n", $Server->code(), ' ', $ServerMsg);
  674         }
  675         $Server->quit();
  676     } else {
  677         die("\n".$0.": Posting failed!\n");
  678     }
  679     return;
  680 }
  681 
  682 
  683 #-------- sub savearticle
  684 # savearticle saves your article to the directory $config{'savedir'}
  685 #
  686 # Receives:
  687 #   - $ArticleR: A reference to an array containing the article
  688 sub savearticle {
  689     my ($ArticleR) = @_;
  690     my $timestamp = timelocal(localtime);
  691     (my $ng = $Newsgroups) =~ s#^Newsgroups:\s*([^,\s]+).*#$1#i;
  692     my $gn = join "", map { substr($_,0,1) } (split(/\./, $ng));
  693     my $filename = $config{'savedir'}."/".$timestamp."-".$gn."-".$$;
  694     open(my $SH, '>', $filename) or die("$0: can't open $filename: $!\n");
  695     print $SH @$ArticleR;
  696     close($SH) or warn "$0: Couldn't close: $!\n";
  697     return;
  698 }
  699 
  700 
  701 #-------- sub signarticle
  702 # signarticle signs an article and returns a reference to an array
  703 #   containing the whole signed Message.
  704 #
  705 # Receives:
  706 #   - $HeaderR: A reference to a hash containing the articles headers.
  707 #   - $BodyR: A reference to an array containing the body.
  708 #
  709 # Returns:
  710 #   - $MessageRef: A reference to an array containing the whole message.
  711 sub signarticle {
  712     my ($HeaderR, $BodyR) = @_;
  713     my (@pgphead, @pgpbody, $pgphead, $pgpbody, $signheaders, @signheaders);
  714 
  715     foreach (@{$config{'PGPSignHeaders'}}) {
  716         if (defined($$HeaderR{lc($_)}) && $$HeaderR{lc($_)} =~ m/^[^\s:]+: .+/o) {
  717             push @signheaders, $_;
  718         }
  719     }
  720 
  721     $pgpbody = join("", @$BodyR);
  722 
  723     # Delete and create the temporary pgp-Files
  724     unlink $config{'pgptmpf'}.".txt";
  725     unlink $config{'pgptmpf'}.".txt.asc";
  726     $signheaders = join(",", @signheaders);
  727 
  728     $pgphead = "X-Signed-Headers: $signheaders\n";
  729     foreach my $header (@signheaders) {
  730         if ($$HeaderR{lc($header)} =~ m/^[^\s:]+: (.+?)\n?$/so) {
  731             $pgphead .= $header.": ".$1."\n";
  732         }
  733     }
  734 
  735     unless (substr($pgpbody,-1,1)=~ /\n/ ) {$pgpbody.="\n"};
  736     open(my $FH, '>', $config{'pgptmpf'} . ".txt") or die("$0: can't open ".$config{'pgptmpf'}.": $!\n");
  737     print $FH $pgphead, "\n", $pgpbody;
  738     print $FH "\n" if ($config{'PGPVersion'} =~ m/GPG/io); # workaround a pgp/gpg incompatibility - should IMHO be fixed in pgpverify
  739     close($FH) or warn "$0: Couldn't close TMP: $!\n";
  740 
  741     # Start PGP, then read the signature;
  742     `$PGPCommand`;
  743 
  744     open($FH, '<', $config{'pgptmpf'} . ".txt.asc") or die("$0: can't open ".$config{'pgptmpf'}.".txt.asc: $!\n");
  745     local $/ = "\n".$config{'pgpbegin'}."\n";
  746     $_ = <$FH>;
  747     unless (m/\Q$config{'pgpbegin'}\E$/o) {
  748         unlink $config{'pgptmpf'} . ".txt";
  749         unlink $config{'pgptmpf'} . ".txt.asc";
  750         die("$0: ".$config{'pgpbegin'}." not found in ".$config{'pgptmpf'}.".txt.asc\n");
  751     }
  752     unlink($config{'pgptmpf'} . ".txt") or warn "$0: Couldn't unlink ".$config{'pgptmpf'}.".txt: $!\n";
  753 
  754     local $/ = "\n";
  755     $_ = <$FH>;
  756     unless (m/^Version: (\S+)(?:\s(\S+))?/o) {
  757         unlink $config{'pgptmpf'} . ".txt.asc";
  758         die("$0: didn't find PGP Version line where expected.\n");
  759     }
  760     if (defined($2)) {
  761         $$HeaderR{$config{'pgpheader'}} = $1."-".$2." ".$signheaders;
  762     } else {
  763         $$HeaderR{$config{'pgpheader'}} = $1." ".$signheaders;
  764     }
  765     do {            # skip other pgp headers like
  766         $_ = <$FH>; # "charset:"||"comment:" until empty line
  767     } while ! /^$/;
  768 
  769     while (<$FH>) {
  770         chomp;
  771         last if /^\Q$config{'pgpend'}\E$/;
  772         $$HeaderR{$config{'pgpheader'}} .= "\n\t$_";
  773     }
  774     $$HeaderR{$config{'pgpheader'}} .= "\n" unless ($$HeaderR{$config{'pgpheader'}} =~ /\n$/s);
  775 
  776     $_ = <$FH>;
  777     unless (eof($FH)) {
  778         unlink $config{'pgptmpf'} . ".txt.asc";
  779         die("$0: unexpected data following ".$config{'pgpend'}."\n");
  780     }
  781     close($FH);
  782     unlink $config{'pgptmpf'} . ".txt.asc";
  783 
  784     my $tmppgpheader = $config{'pgpheader'} . ": " . $$HeaderR{$config{'pgpheader'}};
  785     delete $$HeaderR{$config{'pgpheader'}};
  786 
  787     @pgphead = ();
  788     foreach my $header (@{$config{PGPorderheaders}}) {
  789         if ($$HeaderR{$header} && $$HeaderR{$header} ne "\n") {
  790             push(@pgphead, "$$HeaderR{$header}");
  791             delete $$HeaderR{$header};
  792         }
  793     }
  794 
  795     foreach my $header (keys %$HeaderR) {
  796         if ($$HeaderR{$header} && $$HeaderR{$header} ne "\n") {
  797             push(@pgphead, "$$HeaderR{$header}");
  798             delete $$HeaderR{$header};
  799         }
  800     }
  801 
  802     push @pgphead, ("X-PGP-Hash: " . $config{'digest-algo'} . "\n") if (defined($config{'digest-algo'}));
  803     push @pgphead, ("X-PGP-Key: " . $config{'PGPSigner'} . "\n"), $tmppgpheader;
  804     undef $tmppgpheader;
  805 
  806     @pgpbody = split(/$/m, $pgpbody);
  807     my @pgpmessage = (@pgphead, "\n", @pgpbody);
  808     return \@pgpmessage;
  809 }
  810 
  811 sub version {
  812     print $pname." ".$version."\n";
  813     return;
  814 }
  815 
  816 sub usage {
  817     version();
  818     print "Usage: ".$pname." [OPTS] < article\n";
  819     print "  -a string  set Approved:-header to string\n";
  820     print "  -c string  set Control:-header to string\n";
  821     print "  -d string  set Distribution:-header to string\n";
  822     print "  -e string  set Expires:-header to string\n";
  823     print "  -f string  set From:-header to string\n";
  824     print "  -i string  list of headers to be ignored for signing\n";
  825     print "  -n string  set Newsgroups:-header to string\n";
  826     print "  -o string  set Organization:-header to string\n";
  827     print "  -p port    use port as NNTP port [default=".$config{'NNTPPort'}."]\n";
  828     print "  -r string  set Reply-To:-header to string\n";
  829     print "  -s string  save signed article to directory string instead of posting\n";
  830     print "  -t string  set Subject:-header to string\n";
  831     print "  -v         show version\n";
  832     print "  -w string  set Followup-To:-header to string\n";
  833     print "  -x string  set Path:-header to string\n";
  834     print "  -H         show help\n";
  835     print "  -I         do not add Injection-Date: header\n";
  836     print "  -L         do not add Cancel-Lock: / Cancel-Key: headers\n";
  837     print "  -R         disallow control messages\n";
  838     print "  -S         do not append " . $config{'sig_path'} . "\n";
  839     print "  -X         do not sign article\n";
  840     print "  -Y         force authentication on connect\n";
  841     exit 0;
  842 }
  843 
  844 __END__
  845 
  846 =head1 NAME
  847 
  848 tinews.pl - Post and sign an article via NNTP
  849 
  850 =head1 SYNOPSIS
  851 
  852 B<tinews.pl> [B<OPTIONS>] E<lt> I<input>
  853 
  854 =head1 DESCRIPTION
  855 
  856 B<tinews.pl> reads an article on STDIN, signs it via B<pgp>(1) or
  857 B<gpg>(1) and posts it to a news server.
  858 
  859 If the article contains To:, Cc: or Bcc: headers and mail-actions are
  860 configured it will automatically add a "Posted-And-Mailed: yes" header
  861 to the article and send out the mail-copies.
  862 
  863 If a Cancel-Lock secret file is defined it will automatically add a
  864 Cancel-Lock: (and Cancel-Key: if required) header.
  865 
  866 The input should have unix line endings (<LF>, '\n').
  867 
  868 =head1 OPTIONS
  869 X<tinews, commandline options>
  870 
  871 =over 4
  872 
  873 =item -B<a> C<Approved> | --B<approved> C<Approved>
  874 X<-a> X<--approved>
  875 
  876 Set the article header field Approved: to the given value.
  877 
  878 =item -B<c> C<Control> | --B<control> C<Control>
  879 X<-c> X<--control>
  880 
  881 Set the article header field Control: to the given value.
  882 
  883 =item -B<d> C<Distribution> | --B<distribution> C<Distribution>
  884 X<-d> X<--distribution>
  885 
  886 Set the article header field Distribution: to the given value.
  887 
  888 =item -B<e> C<Expires> | --B<expires> C<Expires>
  889 X<-e> X<--expires>
  890 
  891 Set the article header field Expires: to the given value.
  892 
  893 =item -B<f> C<From> | --B<from> C<From>
  894 X<-f> X<--from>
  895 
  896 Set the article header field From: to the given value.
  897 
  898 =item -B<i> F<header> | --B<ignore-headers> F<header>
  899 X<-i> X<--ignore-headers>
  900 
  901 Comma separated list of headers that will be ignored during signing.
  902 Usually the following headers will be signed if present:
  903 
  904 From, Newsgroups, Subject, Control, Supersedes, Followup-To,
  905 Date, Injection-Date, Sender, Approved, Message-ID, Reply-To,
  906 Cancel-Key, Also-Control and Distribution.
  907 
  908 Some of them may be altered on the Server (i.e. Cancel-Key) which would
  909 invalid the signature, this option can be used the exclude such headers
  910 if required.
  911 
  912 =item -B<n> C<Newsgroups> | --B<newsgroups> C<Newsgroups>
  913 X<-n> X<--newsgroups>
  914 
  915 Set the article header field Newsgroups: to the given value.
  916 
  917 =item -B<o> C<Organization> | --B<organization> C<Organization>
  918 X<-o> X<--organization>
  919 
  920 Set the article header field Organization: to the given value.
  921 
  922 =item -B<p> C<port> | --B<port> C<port>
  923 X<-p> X<--port>
  924 
  925 use C<port> as NNTP-port
  926 
  927 =item -B<r> C<Reply-To> | --B<replyto> C<Reply-To>
  928 X<-r> X<--replyto>
  929 
  930 Set the article header field Reply-To: to the given value.
  931 
  932 =item -B<s> F<directory> | --B<savedir> F<directory>
  933 X<-s> X<--savedir>
  934 
  935 Save signed article to directory F<directory> instead of posting.
  936 
  937 =item -B<t> C<Subject> | --B<subject> C<Subject>
  938 X<-t> X<--subject>
  939 
  940 Set the article header field Subject: to the given value.
  941 
  942 =item -B<v> | --B<version>
  943 X<-v> X<--version>
  944 
  945 Show version.
  946 
  947 =item -B<w> C<Followup-To> | --B<followupto> C<Followup-To>
  948 X<-w> X<--followupto>
  949 
  950 Set the article header field Followup-To: to the given value.
  951 
  952 =item -B<x> C<Path> | --B<path> C<Path>
  953 X<-x> X<--path>
  954 
  955 Set the article header field Path: to the given value.
  956 
  957 =item -B<H> | --B<help>
  958 X<-H> X<--help>
  959 
  960 Show help-page.
  961 
  962 =item -B<I> | --B<no-injection-date>
  963 X<-I> X<--no-injection-date>
  964 
  965 Do not add Injection-Date: header.
  966 
  967 =item -B<L> | --B<no-canlock>
  968 X<-L> X<--no-canlock>
  969 
  970 Do not add Cancel-Lock: / Cancel-Key: headers.
  971 
  972 =item -B<R> | --B<no-control>
  973 X<-R> X<--no-control>
  974 
  975 Restricted mode, disallow control-messages.
  976 
  977 =item -B<S> | --B<no-signature>
  978 X<-s> X<--no-signature>
  979 
  980 Do not append F<$HOME/.signature>.
  981 
  982 =item -B<X> | --B<no-sign>
  983 X<-X> X<--no-sign>
  984 
  985 Do not sign the article.
  986 
  987 =item -B<Y> | --B<force-auth>
  988 X<-Y> X<--force-auth>
  989 
  990 Force authentication on connect even if not required by the server.
  991 
  992 =item -B<A> -B<V> -B<W>
  993 X<-A> X<-V> X<-W>
  994 
  995 These options are accepted for compatibility reasons but ignored.
  996 
  997 =item -B<h> | --B<headers>
  998 X<-h> X<--headers>
  999 
 1000 These options are accepted for compatibility reasons but ignored.
 1001 
 1002 =item -B<O> | --B<no-organization>
 1003 X<-O> X<--no-organization>
 1004 
 1005 These options are accepted for compatibility reasons but ignored.
 1006 
 1007 =item -B<D> | -B<N> | --B<debug>
 1008 X<-D> X<-N> X<--debug>
 1009 
 1010 These options are accepted but do not have any functionality yet.
 1011 
 1012 =back
 1013 
 1014 =head1 EXIT STATUS
 1015 
 1016 The following exit values are returned:
 1017 
 1018 =over 4
 1019 
 1020 =item S< 0>
 1021 
 1022 Successful completion.
 1023 
 1024 =item S<!=0>
 1025 
 1026 An error occurred.
 1027 
 1028 =back
 1029 
 1030 =head1 ENVIRONMENT
 1031 X<tinews, environment variables>
 1032 
 1033 =over 4
 1034 
 1035 =item B<$NNTPSERVER>
 1036 X<$NNTPSERVER> X<NNTPSERVER>
 1037 
 1038 Set to override the NNTP server configured in the source.
 1039 
 1040 =item B<$NNTPPORT>
 1041 X<$NNTPPORT> X<NNTPPORT>
 1042 
 1043 The NNTP TCP-port to post news to. This variable only needs to be set if the
 1044 TCP-port is not 119 (the default). The '-B<p>' command-line option overrides
 1045 B<$NNTPPORT>.
 1046 
 1047 =item B<$PGPPASS>
 1048 X<$PGPPASS> X<PGPPASS>
 1049 
 1050 Set to override the passphrase configured in the source (used for
 1051 B<pgp>(1)-2.6.3).
 1052 
 1053 =item B<$PGPPASSFILE>
 1054 X<$PGPPASSFILE> X<PGPPASSFILE>
 1055 
 1056 Passphrase file used for B<pgp>(1) or B<gpg>(1).
 1057 
 1058 =item B<$SIGNER>
 1059 X<$SIGNER> X<SIGNER>
 1060 
 1061 Set to override the user-id for signing configured in the source. If you
 1062 neither set B<$SIGNER> nor configure it in the source the contents of the
 1063 From:-field will be used.
 1064 
 1065 =item B<$REPLYTO>
 1066 X<$REPLYTO> X<REPLYTO>
 1067 
 1068 Set the article header field Reply-To: to the return address specified by
 1069 the variable if there isn't already a Reply-To: header in the article.
 1070 The '-B<r>' command-line option overrides B<$REPLYTO>.
 1071 
 1072 =item B<$ORGANIZATION>
 1073 X<$ORGANIZATION> X<ORGANIZATION>
 1074 
 1075 Set the article header field Organization: to the contents of the variable
 1076 if there isn't already a Organization: header in the article. The '-B<o>'
 1077 command-line option overrides B<$ORGANIZATION>.
 1078 
 1079 =item B<$DISTRIBUTION>
 1080 X<$DISTRIBUTION> X<DISTRIBUTION>
 1081 
 1082 Set the article header field Distribution: to the contents of the variable
 1083 if there isn't already a Distribution: header in the article. The '-B<d>'
 1084 command-line option overrides B<$DISTRIBUTION>.
 1085 
 1086 =back
 1087 
 1088 =head1 FILES
 1089 
 1090 =over 4
 1091 
 1092 =item F<pgptmp.txt>
 1093 
 1094 Temporary file used to store the reformatted article.
 1095 
 1096 =item F<pgptmp.txt.asc>
 1097 
 1098 Temporary file used to store the reformatted and signed article.
 1099 
 1100 =item F<$PGPPASSFILE>
 1101 
 1102 The passphrase file to be used for B<pgp>(1) or B<gpg>(1).
 1103 
 1104 =item F<$HOME/.signature>
 1105 
 1106 Signature file which will be automatically included.
 1107 
 1108 =item F<$HOME/.cancelsecret>
 1109 
 1110 The passphrase file to be used for Cancel-Locks. This feature is turned
 1111 off by default.
 1112 
 1113 =item F<$HOME/.newsauth>
 1114 
 1115 "nntpserver password [user]" pairs for NNTP servers that require
 1116 authorization. Any line that starts with "#" is a comment. Blank lines are
 1117 ignored. This file should be readable only for the user as it contains the
 1118 user's unencrypted password for reading news. First match counts. If no
 1119 matching entry is found F<$HOME/.nntpauth> is checked.
 1120 
 1121 =item F<$HOME/.nntpauth>
 1122 
 1123 "nntpserver user password" pairs for NNTP servers that require
 1124 authorization. First match counts. Lines starting with "#" are skipped and
 1125 blank lines are ignored. This file should be readable only for the user as
 1126 it contains the user's unencrypted password for reading news.
 1127 F<$HOME/.newsauth> is checked first.
 1128 
 1129 =item F<$XDG_CONFIG_HOME/tinewsrc> F<$HOME/.config/tinewsrc> F<$HOME/.tinewsrc>
 1130 
 1131 "option=value" configuration pairs. Lines that start with "#" are ignored.
 1132 If the file contains unencrypted passwords (e.g. NNTPPass or PGPPass), it
 1133 should be readable for the user only.
 1134 
 1135 =back
 1136 
 1137 =head1 SECURITY
 1138 
 1139 If you've configured or entered a password, even if the variable that
 1140 contained that password has been erased, it may be possible for someone to
 1141 find that password, in plaintext, in a core dump. In short, if serious
 1142 security is an issue, don't use this script.
 1143 
 1144 =head1 NOTES
 1145 
 1146 B<tinews.pl> is designed to be used with B<pgp>(1)-2.6.3,
 1147 B<pgp>(1)-5, B<pgp>(1)-6, B<gpg>(1) and B<gpg2>(1).
 1148 
 1149 B<tinews.pl> requires the following standard modules to be installed:
 1150 B<Getopt::Long>(3pm), B<Net::NNTP>(3pm), B<Time::Local>(3pm) and
 1151 B<Term::Readline>(3pm).
 1152 
 1153 If the Cancel-Lock feature is enabled the following additional modules
 1154 must be installed: B<MIME::Base64>(3pm), B<Digest::SHA>(3pm) or
 1155 B<Digest::SHA1>(3pm) and B<Digest::HMAC_SHA1>(3pm)
 1156 
 1157 =head1 AUTHOR
 1158 
 1159 Urs Janssen E<lt>urs@tin.orgE<gt>,
 1160 Marc Brockschmidt E<lt>marc@marcbrockschmidt.deE<gt>
 1161 
 1162 =head1 SEE ALSO
 1163 
 1164 B<pgp>(1), B<gpg>(1), B<gpg2>(1), B<pgps>(1), B<Digest::HMAC_SHA1>(3pm),
 1165 B<Digest::SHA>(3pm), B<Digest::SHA1>(3pm), B<Getopt::Long>(3pm),
 1166 B<MIME::Base64>(3pm), B<Net::NNTP>(3pm), B<Time::Local>(3pm),
 1167 B<Term::Readline>(3pm)
 1168 
 1169 =cut