"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/tools/tinews.pl" (10 Dec 2017, 42043 Bytes) of package /linux/misc/tin-2.4.2.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.1_vs_2.4.2.

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