"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/tools/tinews.pl" (19 Dec 2018, 42613 Bytes) of package /linux/misc/tin-2.4.3.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.2_vs_2.4.3.

    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-2019 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: - extend debug mode to not delete tmp-files and be more 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.50";
   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 => $config{'debug'}, 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 "  -D         enable debugging\n";
  913     print "  -F string  set References:-header to string\n";
  914     print "  -H         show help\n";
  915     print "  -I         do not add Injection-Date: header\n";
  916     print "  -L         do not add Cancel-Lock: / Cancel-Key: headers\n";
  917     print "  -R         disallow control messages\n";
  918     print "  -S         do not append " . $config{'sig_path'} . "\n";
  919     print "  -X         do not sign article\n";
  920     print "  -Y         force authentication on connect\n";
  921     exit 0;
  922 }
  923 
  924 __END__
  925 
  926 =head1 NAME
  927 
  928 tinews.pl - Post and sign an article via NNTP
  929 
  930 =head1 SYNOPSIS
  931 
  932 B<tinews.pl> [B<OPTIONS>] E<lt> I<input>
  933 
  934 =head1 DESCRIPTION
  935 
  936 B<tinews.pl> reads an article on STDIN, signs it via L<pgp(1)> or
  937 L<gpg(1)> and posts it to a news server.
  938 
  939 The article shall not contain any raw 8-bit data or it needs to
  940 already have the relevant MIME-headers as B<tinews.pl> will not
  941 add any MIME-headers nor encode its input.
  942 
  943 If the article contains To:, Cc: or Bcc: headers and mail-actions are
  944 configured it will automatically add a "Posted-And-Mailed: yes" header
  945 to the article and send out the mail-copies.
  946 
  947 If a Cancel-Lock secret file is defined it will automatically add a
  948 Cancel-Lock: (and Cancel-Key: if required) header.
  949 
  950 The input should have unix line endings (<LF>, '\n').
  951 
  952 =head1 OPTIONS
  953 X<tinews, command-line options>
  954 
  955 =over 4
  956 
  957 =item -B<a> C<Approved> | --B<approved> C<Approved>
  958 X<-a> X<--approved>
  959 
  960 Set the article header field Approved: to the given value.
  961 
  962 =item -B<c> C<Control> | --B<control> C<Control>
  963 X<-c> X<--control>
  964 
  965 Set the article header field Control: to the given value.
  966 
  967 =item -B<d> C<Distribution> | --B<distribution> C<Distribution>
  968 X<-d> X<--distribution>
  969 
  970 Set the article header field Distribution: to the given value.
  971 
  972 =item -B<e> C<Expires> | --B<expires> C<Expires>
  973 X<-e> X<--expires>
  974 
  975 Set the article header field Expires: to the given value.
  976 
  977 =item -B<f> C<From> | --B<from> C<From>
  978 X<-f> X<--from>
  979 
  980 Set the article header field From: to the given value.
  981 
  982 =item -B<i> F<header> | --B<ignore-headers> F<header>
  983 X<-i> X<--ignore-headers>
  984 
  985 Comma separated list of headers that will be ignored during signing.
  986 Usually the following headers will be signed if present:
  987 
  988 From, Newsgroups, Subject, Control, Supersedes, Followup-To,
  989 Date, Injection-Date, Sender, Approved, Message-ID, Reply-To,
  990 Cancel-Key, Also-Control and Distribution.
  991 
  992 Some of them may be altered on the Server (i.e. Cancel-Key) which would
  993 invalid the signature, this option can be used the exclude such headers
  994 if required.
  995 
  996 =item -B<n> C<Newsgroups> | --B<newsgroups> C<Newsgroups>
  997 X<-n> X<--newsgroups>
  998 
  999 Set the article header field Newsgroups: to the given value.
 1000 
 1001 =item -B<o> C<Organization> | --B<organization> C<Organization>
 1002 X<-o> X<--organization>
 1003 
 1004 Set the article header field Organization: to the given value.
 1005 
 1006 =item -B<p> C<port> | --B<port> C<port>
 1007 X<-p> X<--port>
 1008 
 1009 use C<port> as NNTP-port
 1010 
 1011 =item -B<r> C<Reply-To> | --B<replyto> C<Reply-To>
 1012 X<-r> X<--replyto>
 1013 
 1014 Set the article header field Reply-To: to the given value.
 1015 
 1016 =item -B<s> F<directory> | --B<savedir> F<directory>
 1017 X<-s> X<--savedir>
 1018 
 1019 Save signed article to directory F<directory> instead of posting.
 1020 
 1021 =item -B<t> C<Subject> | --B<subject> C<Subject>
 1022 X<-t> X<--subject>
 1023 
 1024 Set the article header field Subject: to the given value.
 1025 
 1026 =item -B<v> | --B<version>
 1027 X<-v> X<--version>
 1028 
 1029 Show version.
 1030 
 1031 =item -B<w> C<Followup-To> | --B<followupto> C<Followup-To>
 1032 X<-w> X<--followupto>
 1033 
 1034 Set the article header field Followup-To: to the given value.
 1035 
 1036 =item -B<x> C<Path> | --B<path> C<Path>
 1037 X<-x> X<--path>
 1038 
 1039 Set the article header field Path: to the given value.
 1040 
 1041 =item -B<H> | --B<help>
 1042 X<-H> X<--help>
 1043 
 1044 Show help-page.
 1045 
 1046 =item -B<I> | --B<no-injection-date>
 1047 X<-I> X<--no-injection-date>
 1048 
 1049 Do not add Injection-Date: header.
 1050 
 1051 =item -B<L> | --B<no-canlock>
 1052 X<-L> X<--no-canlock>
 1053 
 1054 Do not add Cancel-Lock: / Cancel-Key: headers.
 1055 
 1056 =item --B<canlock-algorithm> C<Algorithm>
 1057 X<--canlock-algorithm>
 1058 
 1059 Digest algorithm used for Cancel-Lock: / Cancel-Key: headers.
 1060 Supported algorithms are sha1, sha256 and sha512. Default is sha1.
 1061 
 1062 =item -B<R> | --B<no-control>
 1063 X<-R> X<--no-control>
 1064 
 1065 Restricted mode, disallow control-messages.
 1066 
 1067 =item -B<S> | --B<no-signature>
 1068 X<-s> X<--no-signature>
 1069 
 1070 Do not append F<$HOME/.signature>.
 1071 
 1072 =item -B<X> | --B<no-sign>
 1073 X<-X> X<--no-sign>
 1074 
 1075 Do not sign the article.
 1076 
 1077 =item -B<Y> | --B<force-auth>
 1078 X<-Y> X<--force-auth>
 1079 
 1080 Force authentication on connect even if not required by the server.
 1081 
 1082 =item -B<A> -B<V> -B<W>
 1083 X<-A> X<-V> X<-W>
 1084 
 1085 These options are accepted for compatibility reasons but ignored.
 1086 
 1087 =item -B<h> | --B<headers>
 1088 X<-h> X<--headers>
 1089 
 1090 These options are accepted for compatibility reasons but ignored.
 1091 
 1092 =item -B<O> | --B<no-organization>
 1093 X<-O> X<--no-organization>
 1094 
 1095 These options are accepted for compatibility reasons but ignored.
 1096 
 1097 =item -B<D> | -B<N> | --B<debug>
 1098 X<-D> X<-N> X<--debug>
 1099 
 1100 Enable warnings about raw 8-bit data and set L<Net::NNTP(3pm)> in debug
 1101 mode, enable warnings about raw 8-bit data, warn about disabled options
 1102 due to lacking perl-modules or executables and unreadable files.
 1103 
 1104 =back
 1105 
 1106 =head1 EXIT STATUS
 1107 
 1108 The following exit values are returned:
 1109 
 1110 =over 4
 1111 
 1112 =item S< 0>
 1113 
 1114 Successful completion.
 1115 
 1116 =item S<!=0>
 1117 
 1118 An error occurred.
 1119 
 1120 =back
 1121 
 1122 =head1 ENVIRONMENT
 1123 X<tinews, environment variables>
 1124 
 1125 =over 4
 1126 
 1127 =item B<$NEWSHOST>
 1128 X<$NEWSHOST> X<NEWSHOST>
 1129 
 1130 Set to override the NNTP server configured in the source or config-file.
 1131 It has lower priority than B<$NNTPSERVER> and should be avoided.
 1132 
 1133 =item B<$NNTPSERVER>
 1134 X<$NNTPSERVER> X<NNTPSERVER>
 1135 
 1136 Set to override the NNTP server configured in the source or config-file.
 1137 This has higher priority than B<$NEWSHOST>.
 1138 
 1139 =item B<$NNTPPORT>
 1140 X<$NNTPPORT> X<NNTPPORT>
 1141 
 1142 The NNTP TCP-port to post news to. This variable only needs to be set if the
 1143 TCP-port is not 119 (the default). The '-B<p>' command-line option overrides
 1144 B<$NNTPPORT>.
 1145 
 1146 =item B<$PGPPASS>
 1147 X<$PGPPASS> X<PGPPASS>
 1148 
 1149 Set to override the passphrase configured in the source (used for
 1150 L<pgp(1)>-2.6.3).
 1151 
 1152 =item B<$PGPPASSFILE>
 1153 X<$PGPPASSFILE> X<PGPPASSFILE>
 1154 
 1155 Passphrase file used for L<pgp(1)> or L<gpg(1)>.
 1156 
 1157 =item B<$SIGNER>
 1158 X<$SIGNER> X<SIGNER>
 1159 
 1160 Set to override the user-id for signing configured in the source. If you
 1161 neither set B<$SIGNER> nor configure it in the source the contents of the
 1162 From:-field will be used.
 1163 
 1164 =item B<$REPLYTO>
 1165 X<$REPLYTO> X<REPLYTO>
 1166 
 1167 Set the article header field Reply-To: to the return address specified by
 1168 the variable if there isn't already a Reply-To: header in the article.
 1169 The '-B<r>' command-line option overrides B<$REPLYTO>.
 1170 
 1171 =item B<$ORGANIZATION>
 1172 X<$ORGANIZATION> X<ORGANIZATION>
 1173 
 1174 Set the article header field Organization: to the contents of the variable
 1175 if there isn't already an Organization: header in the article. The '-B<o>'
 1176 command-line option overrides B<$ORGANIZATION>.
 1177 
 1178 =item B<$DISTRIBUTION>
 1179 X<$DISTRIBUTION> X<DISTRIBUTION>
 1180 
 1181 Set the article header field Distribution: to the contents of the variable
 1182 if there isn't already a Distribution: header in the article. The '-B<d>'
 1183 command-line option overrides B<$DISTRIBUTION>.
 1184 
 1185 =back
 1186 
 1187 =head1 FILES
 1188 
 1189 =over 4
 1190 
 1191 =item F<pgptmp.txt>
 1192 
 1193 Temporary file used to store the reformatted article.
 1194 
 1195 =item F<pgptmp.txt.asc>
 1196 
 1197 Temporary file used to store the reformatted and signed article.
 1198 
 1199 =item F<$PGPPASSFILE>
 1200 
 1201 The passphrase file to be used for L<pgp(1)> or L<gpg(1)>.
 1202 
 1203 =item F<$HOME/.signature>
 1204 
 1205 Signature file which will be automatically included.
 1206 
 1207 =item F<$HOME/.cancelsecret>
 1208 
 1209 The passphrase file to be used for Cancel-Locks. This feature is turned
 1210 off by default.
 1211 
 1212 =item F<$HOME/.newsauth>
 1213 
 1214 "nntpserver password [user]" pairs for NNTP servers that require
 1215 authorization. Any line that starts with "#" is a comment. Blank lines are
 1216 ignored. This file should be readable only for the user as it contains the
 1217 user's unencrypted password for reading news. First match counts. If no
 1218 matching entry is found F<$HOME/.nntpauth> is checked.
 1219 
 1220 =item F<$HOME/.nntpauth>
 1221 
 1222 "nntpserver user password" pairs for NNTP servers that require
 1223 authorization. First match counts. Lines starting with "#" are skipped and
 1224 blank lines are ignored. This file should be readable only for the user as
 1225 it contains the user's unencrypted password for reading news.
 1226 F<$HOME/.newsauth> is checked first.
 1227 
 1228 =item F<$XDG_CONFIG_HOME/tinewsrc> F<$HOME/.config/tinewsrc> F<$HOME/.tinewsrc>
 1229 
 1230 "option=value" configuration pairs. Lines that start with "#" are ignored.
 1231 If the file contains unencrypted passwords (e.g. NNTPPass or PGPPass), it
 1232 should be readable for the user only.
 1233 
 1234 =back
 1235 
 1236 =head1 SECURITY
 1237 
 1238 If you've configured or entered a password, even if the variable that
 1239 contained that password has been erased, it may be possible for someone to
 1240 find that password, in plaintext, in a core dump. In short, if serious
 1241 security is an issue, don't use this script.
 1242 
 1243 =head1 NOTES
 1244 
 1245 B<tinews.pl> is designed to be used with L<pgp(1)>-2.6.3,
 1246 L<pgp(1)>-5, L<pgp(1)>-6, L<gpg(1)> and L<gpg2(1)>.
 1247 
 1248 B<tinews.pl> requires the following standard modules to be installed:
 1249 L<Getopt::Long(3pm)>, L<Net::NNTP(3pm)>, <Time::Local(3pm)> and
 1250 L<Term::Readline(3pm)>.
 1251 
 1252 If the Cancel-Lock feature (RFC 8315) is enabled the following additional
 1253 modules must be installed: L<MIME::Base64(3pm)>, L<Digest::SHA(3pm)> or
 1254 L<Digest::SHA1(3pm)> and L<Digest::HMAC_SHA1(3pm)>. sha256 and sha512 as
 1255 algorithms for B<canlock-algorithm> are only available with L<Digest::SHA(3pm)>.
 1256 
 1257 L<gpg2(1)> users may need to set B<$GPG_TTY>, i.e.
 1258 
 1259  GPG_TTY=$(tty)
 1260  export GPG_TTY
 1261 
 1262 before using B<tinews.pl>. See L<https://www.gnupg.org/> for details.
 1263 
 1264 B<tinews.pl> does not do any MIME encoding, its input should be already
 1265 properly encoded and have all relevant headers set.
 1266 
 1267 =head1 AUTHOR
 1268 
 1269 Urs Janssen E<lt>urs@tin.orgE<gt>,
 1270 Marc Brockschmidt E<lt>marc@marcbrockschmidt.deE<gt>
 1271 
 1272 =head1 SEE ALSO
 1273 
 1274 L<pgp(1)>, L<gpg(1)>, L<gpg2(1)>, L<pgps(1)>, L<Digest::HMAC_SHA1(3pm)>,
 1275 L<Digest::SHA(3pm)>, L<Digest::SHA1(3pm)>, L<Getopt::Long(3pm)>,
 1276 L<MIME::Base64(3pm)>, L<Net::NNTP(3pm)>, L<Time::Local(3pm)>,
 1277 L<Term::Readline(3pm)>
 1278 
 1279 =cut