"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/tools/tinews.pl" (20 Nov 2019, 42935 Bytes) of package /linux/misc/tin-2.4.4.tar.xz:


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. For more information about "tinews.pl" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.3_vs_2.4.4.

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