This file is indexed.

/usr/lib/news/bin/pgpverify is in inn2 2.5.2+20110413-1build1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
#! /usr/bin/perl -w
use lib '/usr/share/perl5'; use INN::Config;
# If running inside INN, uncomment the above and point to INN::Config.
#
# Written April 1996, <tale@isc.org> (David C Lawrence)
# Currently maintained by Russ Allbery <rra@stanford.edu>
# Version 1.28, 2007-09-18
#
# NOTICE TO INN MAINTAINERS:  The version that is shipped with INN is the
# same as the version that I make available to the rest of the world
# (including non-INN sites), so please make all changes through me.
#
# This program requires Perl 5, probably at least about Perl 5.003 since
# that's when FileHandle was introduced.  If you want to use this program
# and your Perl is too old, please contact me (rra@stanford.edu) and tell
# me about it; I want to know what old versions of Perl are still used in
# practice.
#
# Changes from 1.27 -> 1.28
# -- Use the INN::Config Perl module instead of innshellvars.pl to
#    accomodate the new build process of INN 2.5.
#
# Changes from 1.26 -> 1.27
# -- Default to pubring.gpg when trustedkeys.gpg is not found in the
#    default key location, for backward compatibility.
#
# Changes from 1.25 -> 1.26
# -- Return the correct status code when the message isn't verified
#    instead of always returning 255.
#
# Changes from 1.24 -> 1.25
# -- Fix the -test switch to actually do something.
# -- Improve date generation when logging to standard output.
#
# Changes from 1.23 -> 1.24
# -- Fix bug in the recognition of wire-format articles.
#
# Changes from 1.15 -> 1.23
# -- Bump version number to match CVS revision number.
# -- Replaced all signature verification code with code that uses detached
#    signatures.  Signatures generated by GnuPG couldn't be verified using
#    attached signatures without adding a Hash: header, and this was the
#    path of least resistance plus avoids munging problems in the future.
#    Code taken from PGP::Sign.
#
# Changes from 1.14 -> 1.15
# -- Added POD documentation.
# -- Fixed the -test switch so that it works again.
# -- Dropped Perl 4 compatibility and reformatted.  Now passes use strict.
#
# Changes from 1.13.1 -> 1.14
# -- Native support for GnuPG without the pgpgpg wrapper, using GnuPG's
#    program interface by Marco d'Itri.
# -- Always use Sys::Syslog without any setlogsock call for Perl 5.6.0 or
#    later, since Sys::Syslog in those versions of Perl uses the C library
#    interface and is now portable.
# -- Default to expecting the key ring in $inn'newsetc/pgp if it exists.
# -- Fix a portability problem for Perl 4 introduced in 1.12.
#
# Changes from 1.13 -> 1.13.1
# -- Nothing functional, just moved the innshellvars.pl line to the head of
#    the script, to accomodate the build process of INN.
#
# Changes from 1.12 -> 1.13
# -- Use INN's syslog_facility if available.
#
# Changes from 1.11 -> 1.12
# -- Support for GnuPG.
# -- Use /usr/ucb/logger, if present, instead of /usr/bin/logger (the latter
#    of which, on Solaris at least, is some sort of brain damaged POSIX.2
#    command which doesn't use syslog).
# -- Made syslog work for dec_osf (version 4, at least).
# -- Fixed up priority of '.' operator vs bitwise operators.
#
# Changes from 1.10 -> 1.11
# -- Code to log error messages to syslog.
#    See $syslog and $syslog_method configurable variables.
# -- Configurably allow date stamp on stderr error messages.
# -- Added locking for multiple concurrent pgp instances.
# -- More clear error message if pgp exits abnormally.
# -- Identify PGP 5 "BAD signature" string.
# -- Minor diddling for INN (path to innshellvars.pl changed).
#
# Changes from 1.9 -> 1.10
# -- Minor diddling for INN 2.0:  use $inn'pathtmp if it exists, and
#    work with the new subst method to find innshellvars.pl.
# -- Do not truncate the tmp file when opening, in case it is really
#    linked to another file.
#
# Changes from 1.8 -> 1.9
# -- Match 'Bad signature' pgp output to return exit status 3 by removing
#    '^' in regexp matched on multiline string.
#
# Changes from 1.7 -> 1.8
# -- Ignore final dot-CRLF if article is in NNTP format.
#
# Changes from 1.6 -> 1.7
# -- Parse PGP 5.0 'good signature' lines.
# -- Allow -test switch; prints pgp input and output.
# -- Look for pgp in INN's innshellvars.pl.
# -- Changed regexp delimiters for stripping $0 to be compatible with old
#    Perl.
#
# Changes from 1.5 -> 1.6
# -- Handle articles encoded in NNTP format ('.' starting line is doubled,
#    \r\n at line end) by stripping NNTP encoding.
# -- Exit 255 with pointer to $HOME or $PGPPATH if pgp can't find key
#    ring.  (It probably doesn't match the necessary error message with
#    ViaCrypt PGP.)
# -- Failures also report Message-ID so the article can be looked up to
#    retry.
#
# Changes from 1.4 -> 1.5
# -- Force English language for 'Good signature from user' by passing
#    +language=en on pgp command line, rather than setting the
#    environment variable LANGUAGE to 'en'.
#
# Changes from 1.3 -> 1.4
# -- Now handles wrapped headers that have been unfolded.
#    (Though I do believe news software oughtn't be unfolding them.)
# -- Checks to ensure that the temporary file is really a file, and
#    not a link or some other weirdness.

# Path to the GnuPG gpgv binary, if you have GnuPG.  If you do, this will
# be used in preference to PGP.  For most current control messages, you
# need a version of GnuPG that can handle RSA signatures.  If you have INN
# and the script is able to successfully include your INN::Config module,
# the value of $INN::Config::gpgv will override this.
# $gpgv = '/usr/local/bin/gpgv';

# Path to pgp binary; for PGP 5.0, set the path to the pgpv binary.  If
# you have INN and the script is able to successfully include your
# INN::Config module, the value of $INN::Config::pgp will override this.
$pgp = '/usr/local/bin/pgp';

# If you keep your keyring somewhere that is not the default used by pgp,
# uncomment the next line and set appropriately.  If you have INN and the
# script is able to successfully include your INN::Config module, this
# will be set to $INN::Config::newsetc/pgp if that directory exists unless
# you set it explicitly.  GnuPG will use a file named pubring.gpg in this
# directory.
# $keyring = '/path/to/your/pgp/config';

# If you have INN and the script is able to successfully include your
# INN::Config module, the value of $INN::Config::pathtmp and
# $INN::Config::locks will override these.
$tmpdir = "/tmp";
$lockdir = $tmpdir;

# How should syslog be accessed?
#
# As it turns out, syslogging is very hard to do portably in versions of
# Perl prior to 5.6.0.  Sys::Syslog should work without difficulty in
# 5.6.0 or later and will be used automatically for those versions of Perl
# (unless $syslog_method is '').  For earlier versions of Perl, 'inet' is
# all that's available up to version 5.004_03.  If your syslog does not
# accept UDP log packets, such as when syslogd runs with the -l flag,
# 'inet' will not work.  A value of 'unix' will try to contact syslogd
# directly over a Unix domain socket built entirely in Perl code (no
# subprocesses).  If that is not working for you, and you have the
# 'logger' program on your system, set this variable to its full path name
# to have a subprocess contact syslogd.  If the method is just "logger",
# the script will search some known directories for that program.  If it
# can't be found & used, everything falls back on stderr logging.
#
# You can test the script's syslogging by running "pgpverify <
# /some/text/file" on a file that is not a valid news article.  The
# "non-header at line #" error should be syslogged.
#
# $syslog_method = 'unix';    # Unix doman socket, Perl 5.004_03 or higher.
# $syslog_method = 'inet';    # UDP to port 514 of localhost.
# $syslog_method = '';        # Don't ever try to do syslogging.
$syslog_method = 'logger';    # Search for the logger program.

# The next two variables are the values to be used for syslog's facility
# and level to use, as would be found in syslog.conf.  For various
# reasons, it is impossible to economically have the script figure out how
# to do syslogging correctly on the machine.  If you have INN and the
# script is able to successfully include you INN::Config module, then
# the value of $INN::Config::syslog_facility will override this value of
# $syslog_facility; $syslog_level is unaffected.
$syslog_facility = 'news';
$syslog_level = 'err';

# Prepend the error message with a timestamp?  This is only relevant if
# not syslogging, when errors go to stderr.
#
# $log_date = 0;  # Zero means don't do it.
# $log_date = 1;  # Non-zero means do it.
$log_date = -t STDOUT; # Do it if STDOUT is to a terminal.

# End of configuration section.


require 5;

use strict;
use vars qw($gpgv $pgp $keyring $tmp $tmpdir $lockdir $syslog_method
            $syslog_facility $syslog_level $log_date $test $messageid);

use Fcntl qw(O_WRONLY O_CREAT O_EXCL);
use FileHandle;
use IPC::Open3 qw(open3);
use POSIX qw(strftime);

# Turn on test mode if the first argument is '-test'.
if (@ARGV && $ARGV[0] eq '-test') {
  shift @ARGV;
  $test = 1;
}

# Not syslogged, such an error is almost certainly from someone running
# the script manually.
die "Usage: $0 < message\n" if @ARGV != 0;

# Grab various defaults from INN::Config if running inside INN.
$pgp = $INN::Config::pgp
    if $INN::Config::pgp && $INN::Config::pgp ne "no-pgp-found-during-configure";
$gpgv = $INN::Config::gpgv if $INN::Config::gpgv;
$tmp = ($INN::Config::pathtmp ? $INN::Config::pathtmp : $tmpdir) . "/pgp$$";
$lockdir = $INN::Config::locks if $INN::Config::locks;
$syslog_facility = $INN::Config::syslog_facility if $INN::Config::syslog_facility;
if (! $keyring && $INN::Config::newsetc) {
  $keyring = $INN::Config::newsetc . '/pgp' if -d $INN::Config::newsetc . '/pgp';
}

# Trim /path/to/prog to prog for error messages.
$0 =~ s%^.*/%%;

# Make sure that the signature verification program can be executed.
if ($gpgv) {
  if (! -x $gpgv) {
    &fail("$0: $gpgv: " . (-e _ ? "cannot execute" : "no such file") . "\n");
  }
} elsif (! -x $pgp) {
  &fail("$0: $pgp: " . (-e _ ? "cannot execute" : "no such file") . "\n");
}

# Parse the article headers and generate the PGP message.
my ($nntp_format, $header, $dup) = &parse_header();
exit 1 unless $$header{'X-PGP-Sig'};
my ($message, $signature, $version)
    = &generate_message($nntp_format, $header, $dup);
if ($test) {
  print "-----MESSAGE-----\n$message\n-----END MESSAGE-----\n\n";
  print "-----SIGNATURE-----\n$signature\n-----SIGNATURE-----\n\n";
}

# The call to pgp needs to be locked because it tries to both read and
# write a file named randseed.bin but doesn't do its own locking as it
# should, and the consequences of a multiprocess conflict is failure to
# verify.
my $lock;
unless ($gpgv) {
  $lock = "$lockdir/LOCK.$0";
  until (&shlock($lock) > 0) {
    sleep(2);
  }
}

# Verify the message.
my ($ok, $signer) = pgp_verify($signature, $version, $message);
unless ($gpgv) {
  unlink ($lock) or &errmsg("$0: unlink $lock: $!\n");
}
print "$signer\n" if $signer;
unless ($ok == 0) {
  &errmsg("$0: verification failed\n");
}
exit $ok;


# Parse the article headers and return a flag saying whether the message
# is in NNTP format and then two references to hashes.  The first hash
# contains all the header/value pairs, and the second contains entries for
# every header that's duplicated.  This is, by design, case-sensitive with
# regards to the headers it checks.  It's also insistent about the
# colon-space rule.
sub parse_header {
  my (%header, %dup, $label, $value, $nntp_format);
  while (<>) {
    # If the first header line ends with \r\n, this article is in the
    # encoding it would be in during an NNTP session.  Some article
    # storage managers keep them this way for efficiency.
    $nntp_format = /\r\n$/ if $. == 1;
    s/\r?\n$//;

    last if /^$/;
    if (/^(\S+):[ \t](.+)/) {
      ($label, $value) = ($1, $2);
      $dup{$label} = 1 if $header{$label};
      $header{$label} = $value;
    } elsif (/^\s/) {
      &fail("$0: non-header at line $.: $_\n") unless $label;
      $header{$label} .= "\n$_";
    } else {
      &fail("$0: non-header at line $.: $_\n");
    }
  }
  $messageid = $header{'Message-ID'};
  return ($nntp_format, \%header, \%dup);
}

# Generate the PGP message to verify.  Takes a flag indicating wire
# format, the hash of headers and header duplicates returned by
# parse_header and returns a list of three elements.  The first is the
# message to verify, the second is the signature, and the third is the
# version number.
sub generate_message {
  my ($nntp_format, $header, $dup) = @_;

  # The regexp below might be too strict about the structure of PGP
  # signature lines.

  # The $sep value means the separator between the radix64 signature lines
  # can have any amount of spaces or tabs, but must have at least one
  # space or tab; if there is a newline then the space or tab has to
  # follow the newline.  Any number of newlines can appear as long as each
  # is followed by at least one space or tab.  *phew*
  my $sep = "[ \t]*(\n?[ \t]+)+";

  # Match all of the characters in a radix64 string.
  my $r64 = '[a-zA-Z0-9+/]';

  local $_ = $$header{'X-PGP-Sig'};
  &fail("$0: X-PGP-Sig not in expected format\n")
    unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/;

  my ($version, $signed_headers, $signature) = ($1, $3, $4);
  $signature =~ s/$sep/\n/g;
  $signature =~ s/^\s+//;

  my $message = "X-Signed-Headers: $signed_headers\n";
  my $label;
  foreach $label (split(",", $signed_headers)) {
    &fail("$0: duplicate signed $label header, can't verify\n")
      if $$dup{$label};
    $message .= "$label: ";
    $message .= "$$header{$label}" if $$header{$label};
    $message .= "\n";
  }
  $message .= "\n";             # end of headers

  while (<>) {                  # read body lines
    if ($nntp_format) {
      # Check for end of article; some news servers (eg, Highwind's
      # "Breeze") include the dot-CRLF of the NNTP protocol in the article
      # data passed to this script.
      last if $_ eq ".\r\n";

      # Remove NNTP encoding.
      s/^\.\./\./;
      s/\r\n$/\n/;
    }
    $message .= $_;
  }

  # Strip off all trailing whitespaces for compatibility with the way that
  # pgpverify used to work, using attached signatures.
  $message =~ s/[ \t]+\n/\n/g;

  return ($message, $signature, $version);
}

# Check a detached signature for given data.  Takes a signature block (in
# the form of an ASCII-armored string with embedded newlines), a version
# number (which may be undef), and the message.  We return an exit status
# and the key id if the signature is verified.  0 means good signature, 1
# means bad data, 2 means an unknown signer, and 3 means a bad signature.
# In the event of an error, we report with errmsg.
#
# This code is taken almost verbatim from PGP::Sign except for the code to
# figure out the PGP style.
sub pgp_verify {
  my ($signature, $version, $message) = @_;
  chomp $signature;

  # Ignore SIGPIPE, since we're going to be talking to PGP.
  local $SIG{PIPE} = 'IGNORE';

  # Set the PGP style based on whether $gpgv is set.
  my $pgpstyle = ($gpgv ? 'GPG' : 'PGP2');

  # Because this is a detached signature, we actually need to save both
  # the signature and the data to files and then run PGP on the signature
  # file to make it verify the signature.  Because this is a detached
  # signature, though, we don't have to do any data mangling, which makes
  # our lives much easier.  It would be nice to do this without having to
  # use temporary files, but I don't see any way to do so without running
  # into mangling problems.
  #
  # PGP v5 *requires* there be some subheader or another.  *sigh*.  So we
  # supply one if Version isn't given.  :)
  my $umask = umask 077;
  my $filename = $tmpdir . '/pgp' . time . '.' . $$;
  my $sigfile = new FileHandle "$filename.asc", O_WRONLY|O_EXCL|O_CREAT;
  unless ($sigfile) {
    &errmsg ("Unable to open temp file $filename.asc: $!\n");
    return (255, undef);
  }
  if ($pgpstyle eq 'PGP2') {
    print $sigfile "-----BEGIN PGP MESSAGE-----\n";
  } else {
    print $sigfile "-----BEGIN PGP SIGNATURE-----\n";
  }
  if (defined $version) {
    print $sigfile "Version: $version\n";
  } elsif ($pgpstyle ne 'GPG') {
    print $sigfile "Comment: Use GnuPG; it's better :)\n";
  }
  print $sigfile "\n", $signature;
  if ($pgpstyle eq 'PGP2') {
    print $sigfile "\n-----END PGP MESSAGE-----\n";
  } else {
    print $sigfile "\n-----END PGP SIGNATURE-----\n";
  }
  close $sigfile;

  # Signature saved.  Now save the actual message.
  my $datafile = new FileHandle "$filename", O_WRONLY|O_EXCL|O_CREAT;
  unless ($datafile) {
    &errmsg ("Unable to open temp file $filename: $!\n");
    unlink "$filename.asc";
    return (255, undef);
  }
  print $datafile $message;
  close $datafile;

  # Figure out what command line we'll be using.
  my @command;
  if ($pgpstyle eq 'GPG') {
    @command = ($gpgv, qw/--quiet --status-fd=1 --logger-fd=1/);
  } else {
    @command = ($pgp, '+batchmode', '+language=en');
  }

  # Now, call PGP to check the signature.  Because we've written
  # everything out to a file, this is actually fairly simple; all we need
  # to do is grab stdout.  PGP prints its banner information to stderr, so
  # just ignore stderr.  Set PGPPATH if desired.
  #
  # For GnuPG, use pubring.gpg if an explicit keyring was configured or
  # found.  Otherwise, use trustedkeys.gpg in the default keyring location
  # if found and non-zero, or fall back on pubring.gpg.  This is
  # definitely not the logic that I would use if writing this from
  # scratch, but it has the most backward compatibility.
  local $ENV{PGPPATH} = $keyring if ($keyring && $pgpstyle ne 'GPG');
  if ($pgpstyle eq 'GPG') {
    if ($keyring) {
      push (@command, "--keyring=$keyring/pubring.gpg");
    } else {
      my $home = $ENV{GNUPGHOME} || $ENV{HOME};
      $home .= '/.gnupg' if $home;
      if ($home && ! -s "$home/trustedkeys.gpg" && -f "$home/pubring.gpg") {
        push (@command, "--keyring=pubring.gpg");
      }
    }
  }
  push (@command, "$filename.asc");
  push (@command, $filename);
  my $input = new FileHandle;
  my $output = new FileHandle;
  my $pid = eval { open3 ($input, $output, $output, @command) };
  if ($@) {
    &errmsg ($@);
    &errmsg ("Execution of $command[0] failed.\n");
    unlink ($filename, "$filename.asc");
    return (255, undef);
  }
  close $input;

  # Check for the message that gives us the key status and return the
  # appropriate thing to our caller.  This part is a zoo due to all of the
  # different formats used.  GPG has finally done the right thing and
  # implemented a separate status stream with parseable data.
  #
  # MIT PGP 2.6.2 and PGP 6.5.2:
  #   Good signature from user "Russ Allbery <rra@stanford.edu>".
  # ViaCrypt PGP 4.0:
  #   Good signature from user:  Russ Allbery <rra@stanford.edu>
  # PGP 5.0:
  #   Good signature made 1999-02-10 03:29 GMT by key:
  #     1024 bits, Key ID 0AFC7476, Created 1999-02-10
  #      "Russ Allbery <rra@stanford.edu>"
  #
  # Also, PGP v2 prints out "Bad signature" while PGP v5 uses "BAD
  # signature", and PGP v6 reverts back to "Bad signature".
  local $_;
  local $/ = '';
  my $signer;
  my $ok = 255;
  while (<$output>) {
    print if $test;
    if ($pgpstyle eq 'GPG') {
      if (/\[GNUPG:\]\s+GOODSIG\s+\S+\s+(\S+)/) {
        $ok = 0;
        $signer = $1;
      } elsif (/\[GNUPG:\]\s+NODATA/ || /\[GNUPG:\]\s+UNEXPECTED/) {
        $ok = 1;
      } elsif (/\[GNUPG:\]\s+NO_PUBKEY/) {
        $ok = 2;
      } elsif (/\[GNUPG:\]\s+BADSIG\s+/) {
        $ok = 3;
      }
    } else {
      if (/^Good signature from user(?::\s+(.*)|\s+\"(.*)\"\.)$/m) {
        $signer = $+;
        $ok = 0;
        last;
      } elsif (/^Good signature made .* by key:\n.+\n\s+\"(.*)\"/m) {
        $signer = $1;
        $ok = 0;
        last;
      } elsif (/^\S+: Good signature from \"(.*)\"/m) {
        $signer = $1;
        $ok = 0;
        last;
      } elsif (/^(?:\S+: )?Bad signature /im) {
        $ok = 3;
        last;
      }
    }
  }
  close $input;
  waitpid ($pid, 0);
  unlink ($filename, "$filename.asc");
  umask $umask;
  return ($ok, $signer || '');
}

# Log an error message, attempting syslog first based on $syslog_method
# and falling back on stderr.
sub errmsg {
  my ($message) = @_;
  $message =~ s/\n$//;

  my $date = '';
  if ($log_date) {
    $date = strftime ('%Y-%m-%d %T ', localtime);
  }

  if ($syslog_method && $] >= 5.006) {
    eval "use Sys::Syslog";
    $syslog_method = 'internal';
  }

  if ($syslog_method eq "logger") {
    my @loggers = ('/usr/ucb/logger', '/usr/bin/logger',
                   '/usr/local/bin/logger');
    my $try;
    foreach $try (@loggers) {
      if (-x $try) {
        $syslog_method = $try;
        last;
      }
    }
    $syslog_method = '' if $syslog_method eq 'logger';
  }

  if ($syslog_method ne '' && $syslog_method !~ m%/logger$%) {
    eval "use Sys::Syslog";
  }

  if ($@ || $syslog_method eq '') {
    warn $date, "$0: trying to use Perl's syslog: $@\n" if $@;
    warn $date, $message, "\n";
    warn $date, "... while processing $messageid\n"
      if $messageid;

  } else {
    $message .= " processing $messageid"
      if $messageid;

    if ($syslog_method =~ m%/logger$%) {
      unless (system($syslog_method, "-i", "-p",
                     "$syslog_facility.$syslog_level", $message) == 0) {
        if ($? >> 8) {
          warn $date, "$0: $syslog_method exited status ",  $? >>  8, "\n";
        } else {
          warn $date, "$0: $syslog_method died on signal ", $? & 255, "\n";
        }
        $syslog_method = '';
        &errmsg($message);
      }

    } else {
      # setlogsock arrived in Perl 5.004_03 to enable Sys::Syslog to use a
      # Unix domain socket to talk to syslogd, which is the only way to do
      # it when syslog runs with the -l switch.
      if ($syslog_method eq "unix") {
        if ($^O eq "dec_osf" && $] >= 5) {
          eval 'sub Sys::Syslog::_PATH_LOG { "/dev/log" }';
        }
        if ($] <= 5.00403 || ! eval "setlogsock('unix')") {
          warn $date, "$0: cannot use syslog_method 'unix' on this system\n";
          $syslog_method = '';
          &errmsg($message);
          return;
        }
      }

      # Unfortunately, there is no way to definitively know in this
      # program if the message was logged.  I wish there were a way to
      # send a message to stderr if and only if the syslog attempt failed.
      &openlog($0, 'pid', $syslog_facility);
      &syslog($syslog_level, $_[0]);
      &closelog();
    }
  }
}

sub fail {
  &errmsg($_[0]);
  exit 255;
}

# Get a lock in essentially the same fashion as INN's shlock.  return 1 on
# success, 0 for normal failure, -1 for abnormal failure.  "normal
# failure" is that a lock is apparently in use by someone else.
sub shlock {
  my ($file) = @_;
  my ($ltmp, $pid);

  unless (defined(&ENOENT)) {
    eval "require POSIX qw(:errno_h)";
    if ($@) {
      # values taken from BSD/OS 3.1
      sub ENOENT {  2 }
      sub ESRCH  {  3 }
      sub EEXIST { 17 }
    }
  }

  $ltmp = ($file =~ m%(.*/)%)[0] . "shlock$$";

  # This should really attempt to use another temp name.
  -e $ltmp && (unlink($ltmp) || return -1);

  open(LTMP, ">$ltmp") || return -1;
  print LTMP "$$\n" || (unlink($ltmp), return -1);
  close(LTMP) || (unlink($ltmp), return -1);

  if (!link($ltmp, $file)) {
    if ($! == &EEXIST) {
      if (open(LOCK, "<$file")) {
        $pid = <LOCK>;
        if ($pid =~ /^\d+$/ && (kill(0, $pid) == 1 || $! != &ESRCH)) {
          unlink($ltmp);
          return 0;
        }

        # OK, the pid in the lockfile is not a number or no longer exists.
        close(LOCK);            # silent failure is ok here

        # Unlink failed.
        if (unlink($file) != 1 && $! != &ENOENT) {
          unlink($ltmp);
          return 0;
        }

      # Check if open failed for reason other than file no longer present.
      } elsif ($! != &ENOENT) {
        unlink($ltmp);
        return -1;
      }

      # Either this process unlinked the lockfile because it was bogus, or
      # between this process's link() and open() the other process holding
      # the lock unlinked it.  This process can now try to acquire.
      if (! link($ltmp, $file)) {
        unlink($ltmp);
        return $! == &EEXIST ? 0 : -1; # Maybe another proc grabbed the lock.
      }

    } else {                    # First attempt to link failed.
      unlink($ltmp);
      return 0;
    }
  }
  unlink($ltmp);
  return 1;
}

=head1 NAME

pgpverify - Cryptographically verify Usenet control messages

=head1 SYNOPSIS

B<pgpverify> [B<-test>] < I<message>

=head1 DESCRIPTION

The B<pgpverify> program reads (on standard input) a Usenet control
message that has been cryptographically signed using the B<signcontrol>
program (or some other program that produces a compatible format).
B<pgpverify> then uses a PGP implementation to determine who signed the
control message.  If the control message has a valid signature,
B<pgpverify> prints (to stdout) the user ID of the key that signed the
message.  Otherwise, it exits with a non-zero exit status.

If B<pgpverify> is installed as part of INN, it uses INN's configuration
to determine what signature verification program to use, how to log
errors, what temporary directory to use, and what keyring to use.
Otherwise, all of those parameters can be set by editing the beginning of
this script.

By default, when running as part of INN, B<pgpverify> expects the PGP key
ring to be found in I<pathetc>/pgp (as either F<pubring.pgp> or
F<pubring.gpg> depending on whether PGP or GnuPG is used to verify
signatures).  If that directory doesn't exist, it will fall back on using
the default key ring, which is in a F<.pgp> or F<.gnupg> subdirectory of
the running user's home directory.

INN, when using GnuPG, configures B<pgpverify> to use B<gpgv>, which by
default expects keys to be in a keyring named F<trustedkeys.gpg>, since it
doesn't implement trust checking directly.  B<pgpverify> uses that file if
present but falls back to F<pubring.gpg> if it's not found.  This bypasses
the trust model for checking keys, but is compatible with the way that
B<pgpverify> used to behave.  Of course, if a keyring is found in
I<pathetc>/pgp or configured at the top of the script, that overrides all of
this behavior.

=head1 OPTIONS

The B<-test> flag causes B<pgpverify> to print out the input that it is
passing to PGP (which is a reconstructed version of the input that
supposedly created the control message) as well as the output from PGP's
analysis of the message.

=head1 EXIT STATUS

B<pgpverify> may exit with the following statuses:

=over 4

=item 0Z<>

The control message had a good PGP signature.

=item 1

The control message had no PGP signature.

=item 2

The control message had an unknown PGP signature.

=item 3

The control message had a bad PGP signature.

=item 255

A problem occurred not directly related to PGP analysis of signature.

=back

=head1 ENVIRONMENT

B<pgpverify> does not modify or otherwise alter the environment before
invoking the B<pgp> or B<gpgv> program.  It is the responsibility of the
person who installs B<pgpverify> to ensure that when B<pgp> or B<gpgv>
runs, it has the ability to locate and read a PGP key file that contains
the PGP public keys for the appropriate Usenet hierarchy administrators.
B<pgpverify> can be pointed to an appropriate key ring by editing
variables at the beginning of this script.

=head1 NOTES

Historically, Usenet news server administrators have configured their news
servers to automatically honor Usenet control messages based on the
originator of the control messages and the hierarchies for which the
control messages applied.  For example, in the past, David Lawrence always
issued control messages for the S<"Big 8"> hierarchies (comp, humanities,
misc, news, rec, sci, soc, talk).  Usenet news administrators would
configure their news server software to automatically honor newgroup and
rmgroup control messages that originated from David Lawrence and applied
to any of the S<Big 8> hierarchies.

Unfortunately, Usenet news articles (including control messages) are
notoriously easy to forge.  Soon, malicious users realized they could
create or remove (at least temporarily) any S<Big 8> newsgroup they wanted by
simply forging an appropriate control message in David Lawrence's name.
As Usenet became more widely used, forgeries became more common.

The B<pgpverify> program was designed to allow Usenet news administrators
to configure their servers to cryptographically verify control messages
before automatically acting on them.  Under the B<pgpverify> system, a Usenet
hierarchy maintainer creates a PGP public/private key pair and
disseminates the public key.  Whenever the hierarchy maintainer issues a
control message, he uses the B<signcontrol> program to sign the control
message with the PGP private key.  Usenet news administrators configure
their news servers to run the B<pgpverify> program on the appropriate
control messages, and take action based on the PGP key User ID that signed
the control message, not the name and address that appear in the control
message's From: or Sender: headers.

Thus, appropriate use of the B<signcontrol> and B<pgpverify> programs
essentially eliminates the possibility of malicious users forging Usenet
control messages that sites will act upon, as such users would have to
obtain the PGP private key in order to forge a control message that would
pass the cryptographic verification step.  If the hierarchy administrators
properly protect their PGP private keys, the only way a malicious user
could forge a validly-signed control message would be by breaking the
public key encryption algorithm, which (at least at this time) is believed
to be prohibitively difficult for PGP keys of a sufficient bit length.

=head1 HISTORY

B<pgpverify> was written by David C Lawrence <tale@isc.org>.  Manual page
provided by James Ralston.  It is currently maintained by Russ Allbery
<rra@stanford.edu>.

=head1 COPYRIGHT AND LICENSE

David Lawrence wrote:  "Our lawyer told me to include the following.  The
upshot of it is that you can use the software for free as much as you
like."

Copyright (c) 1996 UUNET Technologies, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

=over 4

=item 1.

Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

=item 2.

Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

=item 3.

All advertising materials mentioning features or use of this software must
display the following acknowledgement:

  This product includes software developed by UUNET Technologies, Inc.

=item 4.

The name of UUNET Technologies ("UUNET") may not be used to endorse or
promote products derived from this software without specific prior written
permission.

=back

THIS SOFTWARE IS PROVIDED BY UUNET "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
NO EVENT SHALL UUNET BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=head1 SEE ALSO

gpgv(1), pgp(1).

L<ftp://ftp.isc.org/pub/pgpcontrol/> is where the most recent versions of
B<signcontrol> and B<pgpverify> live, along with PGP public keys used for
hierarchy administration.

=cut

# Local variables:
# cperl-indent-level: 2
# fill-column: 74
# End: