chiark / gitweb /
some todos done
[innduct.git] / control / pgpverify.in
1 #! /usr/bin/perl -w
2 # do '@LIBDIR@/innshellvars.pl';
3 # If running inside INN, uncomment the above and point to innshellvars.pl.
4 #
5 # Written April 1996, <tale@isc.org> (David C Lawrence)
6 # Currently maintained by Russ Allbery <rra@stanford.edu>
7 # Version 1.27, 2005-07-02
8 #
9 # NOTICE TO INN MAINTAINERS:  The version that is shipped with INN is the
10 # same as the version that I make available to the rest of the world
11 # (including non-INN sites), so please make all changes through me.
12 #
13 # This program requires Perl 5, probably at least about Perl 5.003 since
14 # that's when FileHandle was introduced.  If you want to use this program
15 # and your Perl is too old, please contact me (rra@stanford.edu) and tell
16 # me about it; I want to know what old versions of Perl are still used in
17 # practice.
18 #
19 # Changes from 1.26 -> 1.27
20 # -- Default to pubring.gpg when trustedkeys.gpg is not found in the
21 #    default key location, for backward compatibility.
22 #
23 # Changes from 1.25 -> 1.26
24 # -- Return the correct status code when the message isn't verified
25 #    instead of always returning 255.
26 #
27 # Changes from 1.24 -> 1.25
28 # -- Fix the -test switch to actually do something.
29 # -- Improve date generation when logging to standard output.
30 #
31 # Changes from 1.23 -> 1.24
32 # -- Fix bug in the recognition of wire-format articles.
33 #
34 # Changes from 1.15 -> 1.23
35 # -- Bump version number to match CVS revision number.
36 # -- Replaced all signature verification code with code that uses detached
37 #    signatures.  Signatures generated by GnuPG couldn't be verified using
38 #    attached signatures without adding a Hash: header, and this was the
39 #    path of least resistance plus avoids munging problems in the future.
40 #    Code taken from PGP::Sign.
41 #
42 # Changes from 1.14 -> 1.15
43 # -- Added POD documentation.
44 # -- Fixed the -test switch so that it works again.
45 # -- Dropped Perl 4 compatibility and reformatted.  Now passes use strict.
46 #
47 # Changes from 1.13.1 -> 1.14
48 # -- Native support for GnuPG without the pgpgpg wrapper, using GnuPG's
49 #    program interface by Marco d'Itri.
50 # -- Always use Sys::Syslog without any setlogsock call for Perl 5.6.0 or
51 #    later, since Sys::Syslog in those versions of Perl uses the C library
52 #    interface and is now portable.
53 # -- Default to expecting the key ring in $inn'newsetc/pgp if it exists.
54 # -- Fix a portability problem for Perl 4 introduced in 1.12.
55 #
56 # Changes from 1.13 -> 1.13.1
57 # -- Nothing functional, just moved the innshellvars.pl line to the head of
58 #    the script, to accomodate the build process of INN.
59 #
60 # Changes from 1.12 -> 1.13
61 # -- Use INN's syslog_facility if available.
62 #
63 # Changes from 1.11 -> 1.12
64 # -- Support for GnuPG.
65 # -- Use /usr/ucb/logger, if present, instead of /usr/bin/logger (the latter
66 #    of which, on Solaris at least, is some sort of brain damaged POSIX.2
67 #    command which doesn't use syslog).
68 # -- Made syslog work for dec_osf (version 4, at least).
69 # -- Fixed up priority of '.' operator vs bitwise operators.
70 #
71 # Changes from 1.10 -> 1.11
72 # -- Code to log error messages to syslog.
73 #    See $syslog and $syslog_method configurable variables.
74 # -- Configurably allow date stamp on stderr error messages.
75 # -- Added locking for multiple concurrent pgp instances.
76 # -- More clear error message if pgp exits abnormally.
77 # -- Identify PGP 5 "BAD signature" string.
78 # -- Minor diddling for INN (path to innshellvars.pl changed).
79 #
80 # Changes from 1.9 -> 1.10
81 # -- Minor diddling for INN 2.0:  use $inn'pathtmp if it exists, and
82 #    work with the new subst method to find innshellvars.pl.
83 # -- Do not truncate the tmp file when opening, in case it is really
84 #    linked to another file.
85 #
86 # Changes from 1.8 -> 1.9
87 # -- Match 'Bad signature' pgp output to return exit status 3 by removing
88 #    '^' in regexp matched on multiline string.
89 #
90 # Changes from 1.7 -> 1.8
91 # -- Ignore final dot-CRLF if article is in NNTP format.
92 #
93 # Changes from 1.6 -> 1.7
94 # -- Parse PGP 5.0 'good signature' lines.
95 # -- Allow -test switch; prints pgp input and output.
96 # -- Look for pgp in INN's innshellvars.pl.
97 # -- Changed regexp delimiters for stripping $0 to be compatible with old
98 #    Perl.
99 #
100 # Changes from 1.5 -> 1.6
101 # -- Handle articles encoded in NNTP format ('.' starting line is doubled,
102 #    \r\n at line end) by stripping NNTP encoding.
103 # -- Exit 255 with pointer to $HOME or $PGPPATH if pgp can't find key
104 #    ring.  (It probably doesn't match the necessary error message with
105 #    ViaCrypt PGP.)
106 # -- Failures also report Message-ID so the article can be looked up to
107 #    retry.
108 #
109 # Changes from 1.4 -> 1.5
110 # -- Force English language for 'Good signature from user' by passing
111 #    +language=en on pgp command line, rather than setting the
112 #    environment variable LANGUAGE to 'en'.
113 #
114 # Changes from 1.3 -> 1.4
115 # -- Now handles wrapped headers that have been unfolded.
116 #    (Though I do believe news software oughtn't be unfolding them.)
117 # -- Checks to ensure that the temporary file is really a file, and
118 #    not a link or some other weirdness.
119
120 # Path to the GnuPG gpgv binary, if you have GnuPG.  If you do, this will
121 # be used in preference to PGP.  For most current control messages, you
122 # need a version of GnuPG that can handle RSA signatures.  If you have INN
123 # and the script is able to successfully include your innshellvars.pl
124 # file, the value of $inn::gpgv will override this.
125 # $gpgv = '/usr/local/bin/gpgv';
126
127 # Path to pgp binary; for PGP 5.0, set the path to the pgpv binary.  If
128 # you have INN and the script is able to successfully include your
129 # innshellvars.pl file, the value of $inn::pgp will override this.
130 $pgp = '/usr/local/bin/pgp';
131
132 # If you keep your keyring somewhere that is not the default used by pgp,
133 # uncomment the next line and set appropriately.  If you have INN and the
134 # script is able to successfully include your innshellvars.pl file, this
135 # will be set to $inn::newsetc/pgp if that directory exists unless you set
136 # it explicitly.  GnuPG will use a file named pubring.gpg in this
137 # directory.
138 # $keyring = '/path/to/your/pgp/config';
139
140 # If you have INN and the script is able to successfully include your
141 # innshellvars.pl file, the value of $inn::pathtmp and $inn::locks will
142 # override these.
143 $tmpdir = "/tmp";
144 $lockdir = $tmpdir;
145
146 # How should syslog be accessed?
147 #
148 # As it turns out, syslogging is very hard to do portably in versions of
149 # Perl prior to 5.6.0.  Sys::Syslog should work without difficulty in
150 # 5.6.0 or later and will be used automatically for those versions of Perl
151 # (unless $syslog_method is '').  For earlier versions of Perl, 'inet' is
152 # all that's available up to version 5.004_03.  If your syslog does not
153 # accept UDP log packets, such as when syslogd runs with the -l flag,
154 # 'inet' will not work.  A value of 'unix' will try to contact syslogd
155 # directly over a Unix domain socket built entirely in Perl code (no
156 # subprocesses).  If that is not working for you, and you have the
157 # 'logger' program on your system, set this variable to its full path name
158 # to have a subprocess contact syslogd.  If the method is just "logger",
159 # the script will search some known directories for that program.  If it
160 # can't be found & used, everything falls back on stderr logging.
161 #
162 # You can test the script's syslogging by running "pgpverify <
163 # /some/text/file" on a file that is not a valid news article.  The
164 # "non-header at line #" error should be syslogged.
165 #
166 # $syslog_method = 'unix';    # Unix doman socket, Perl 5.004_03 or higher.
167 # $syslog_method = 'inet';    # UDP to port 514 of localhost.
168 # $syslog_method = '';        # Don't ever try to do syslogging.
169 $syslog_method = 'logger';    # Search for the logger program.
170
171 # The next two variables are the values to be used for syslog's facility
172 # and level to use, as would be found in syslog.conf.  For various
173 # reasons, it is impossible to economically have the script figure out how
174 # to do syslogging correctly on the machine.  If you have INN and the
175 # script is able to successfully include you innshellvars.pl file, then
176 # the value of $inn::syslog_facility will override this value of
177 # $syslog_facility; $syslog_level is unaffected.
178 $syslog_facility = 'news';
179 $syslog_level = 'err';
180
181 # Prepend the error message with a timestamp?  This is only relevant if
182 # not syslogging, when errors go to stderr.
183 #
184 # $log_date = 0;  # Zero means don't do it.
185 # $log_date = 1;  # Non-zero means do it.
186 $log_date = -t STDOUT; # Do it if STDOUT is to a terminal.
187
188 # End of configuration section.
189
190
191 require 5;
192
193 use strict;
194 use vars qw($gpgv $pgp $keyring $tmp $tmpdir $lockdir $syslog_method
195             $syslog_facility $syslog_level $log_date $test $messageid);
196
197 use Fcntl qw(O_WRONLY O_CREAT O_EXCL);
198 use FileHandle;
199 use IPC::Open3 qw(open3);
200 use POSIX qw(strftime);
201
202 # Turn on test mode if the first argument is '-test'.
203 if (@ARGV && $ARGV[0] eq '-test') {
204   shift @ARGV;
205   $test = 1;
206 }
207
208 # Not syslogged, such an error is almost certainly from someone running
209 # the script manually.
210 die "Usage: $0 < message\n" if @ARGV != 0;
211
212 # Grab various defaults from innshellvars.pl if running inside INN.
213 $pgp = $inn::pgp
214     if $inn::pgp && $inn::pgp ne "no-pgp-found-during-configure";
215 $gpgv = $inn::gpgv if $inn::gpgv;
216 $tmp = ($inn::pathtmp ? $inn::pathtmp : $tmpdir) . "/pgp$$";
217 $lockdir = $inn::locks if $inn::locks;
218 $syslog_facility = $inn::syslog_facility if $inn::syslog_facility;
219 if (! $keyring && $inn::newsetc) {
220   $keyring = $inn::newsetc . '/pgp' if -d $inn::newsetc . '/pgp';
221 }
222
223 # Trim /path/to/prog to prog for error messages.
224 $0 =~ s%^.*/%%;
225
226 # Make sure that the signature verification program can be executed.
227 if ($gpgv) {
228   if (! -x $gpgv) {
229     &fail("$0: $gpgv: " . (-e _ ? "cannot execute" : "no such file") . "\n");
230   }
231 } elsif (! -x $pgp) {
232   &fail("$0: $pgp: " . (-e _ ? "cannot execute" : "no such file") . "\n");
233 }
234
235 # Parse the article headers and generate the PGP message.
236 my ($nntp_format, $header, $dup) = &parse_header();
237 exit 1 unless $$header{'X-PGP-Sig'};
238 my ($message, $signature, $version)
239     = &generate_message($nntp_format, $header, $dup);
240 if ($test) {
241   print "-----MESSAGE-----\n$message\n-----END MESSAGE-----\n\n";
242   print "-----SIGNATURE-----\n$signature\n-----SIGNATURE-----\n\n";
243 }
244
245 # The call to pgp needs to be locked because it tries to both read and
246 # write a file named randseed.bin but doesn't do its own locking as it
247 # should, and the consequences of a multiprocess conflict is failure to
248 # verify.
249 my $lock;
250 unless ($gpgv) {
251   $lock = "$lockdir/LOCK.$0";
252   until (&shlock($lock) > 0) {
253     sleep(2);
254   }
255 }
256
257 # Verify the message.
258 my ($ok, $signer) = pgp_verify($signature, $version, $message);
259 unless ($gpgv) {
260   unlink ($lock) or &errmsg("$0: unlink $lock: $!\n");
261 }
262 print "$signer\n" if $signer;
263 unless ($ok == 0) {
264   &errmsg("$0: verification failed\n");
265 }
266 exit $ok;
267
268
269 # Parse the article headers and return a flag saying whether the message
270 # is in NNTP format and then two references to hashes.  The first hash
271 # contains all the header/value pairs, and the second contains entries for
272 # every header that's duplicated.  This is, by design, case-sensitive with
273 # regards to the headers it checks.  It's also insistent about the
274 # colon-space rule.
275 sub parse_header {
276   my (%header, %dup, $label, $value, $nntp_format);
277   while (<>) {
278     # If the first header line ends with \r\n, this article is in the
279     # encoding it would be in during an NNTP session.  Some article
280     # storage managers keep them this way for efficiency.
281     $nntp_format = /\r\n$/ if $. == 1;
282     s/\r?\n$//;
283
284     last if /^$/;
285     if (/^(\S+):[ \t](.+)/) {
286       ($label, $value) = ($1, $2);
287       $dup{$label} = 1 if $header{$label};
288       $header{$label} = $value;
289     } elsif (/^\s/) {
290       &fail("$0: non-header at line $.: $_\n") unless $label;
291       $header{$label} .= "\n$_";
292     } else {
293       &fail("$0: non-header at line $.: $_\n");
294     }
295   }
296   $messageid = $header{'Message-ID'};
297   return ($nntp_format, \%header, \%dup);
298 }
299
300 # Generate the PGP message to verify.  Takes a flag indicating wire
301 # format, the hash of headers and header duplicates returned by
302 # parse_header and returns a list of three elements.  The first is the
303 # message to verify, the second is the signature, and the third is the
304 # version number.
305 sub generate_message {
306   my ($nntp_format, $header, $dup) = @_;
307
308   # The regexp below might be too strict about the structure of PGP
309   # signature lines.
310
311   # The $sep value means the separator between the radix64 signature lines
312   # can have any amount of spaces or tabs, but must have at least one
313   # space or tab; if there is a newline then the space or tab has to
314   # follow the newline.  Any number of newlines can appear as long as each
315   # is followed by at least one space or tab.  *phew*
316   my $sep = "[ \t]*(\n?[ \t]+)+";
317
318   # Match all of the characters in a radix64 string.
319   my $r64 = '[a-zA-Z0-9+/]';
320
321   local $_ = $$header{'X-PGP-Sig'};
322   &fail("$0: X-PGP-Sig not in expected format\n")
323     unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/;
324
325   my ($version, $signed_headers, $signature) = ($1, $3, $4);
326   $signature =~ s/$sep/\n/g;
327   $signature =~ s/^\s+//;
328
329   my $message = "X-Signed-Headers: $signed_headers\n";
330   my $label;
331   foreach $label (split(",", $signed_headers)) {
332     &fail("$0: duplicate signed $label header, can't verify\n")
333       if $$dup{$label};
334     $message .= "$label: ";
335     $message .= "$$header{$label}" if $$header{$label};
336     $message .= "\n";
337   }
338   $message .= "\n";             # end of headers
339
340   while (<>) {                  # read body lines
341     if ($nntp_format) {
342       # Check for end of article; some news servers (eg, Highwind's
343       # "Breeze") include the dot-CRLF of the NNTP protocol in the article
344       # data passed to this script.
345       last if $_ eq ".\r\n";
346
347       # Remove NNTP encoding.
348       s/^\.\./\./;
349       s/\r\n$/\n/;
350     }
351     $message .= $_;
352   }
353
354   # Strip off all trailing whitespaces for compatibility with the way that
355   # pgpverify used to work, using attached signatures.
356   $message =~ s/[ \t]+\n/\n/g;
357
358   return ($message, $signature, $version);
359 }
360
361 # Check a detached signature for given data.  Takes a signature block (in
362 # the form of an ASCII-armored string with embedded newlines), a version
363 # number (which may be undef), and the message.  We return an exit status
364 # and the key id if the signature is verified.  0 means good signature, 1
365 # means bad data, 2 means an unknown signer, and 3 means a bad signature.
366 # In the event of an error, we report with errmsg.
367 #
368 # This code is taken almost verbatim from PGP::Sign except for the code to
369 # figure out the PGP style.
370 sub pgp_verify {
371   my ($signature, $version, $message) = @_;
372   chomp $signature;
373
374   # Ignore SIGPIPE, since we're going to be talking to PGP.
375   local $SIG{PIPE} = 'IGNORE';
376
377   # Set the PGP style based on whether $gpgv is set.
378   my $pgpstyle = ($gpgv ? 'GPG' : 'PGP2');
379
380   # Because this is a detached signature, we actually need to save both
381   # the signature and the data to files and then run PGP on the signature
382   # file to make it verify the signature.  Because this is a detached
383   # signature, though, we don't have to do any data mangling, which makes
384   # our lives much easier.  It would be nice to do this without having to
385   # use temporary files, but I don't see any way to do so without running
386   # into mangling problems.
387   #
388   # PGP v5 *requires* there be some subheader or another.  *sigh*.  So we
389   # supply one if Version isn't given.  :)
390   my $umask = umask 077;
391   my $filename = $tmpdir . '/pgp' . time . '.' . $$;
392   my $sigfile = new FileHandle "$filename.asc", O_WRONLY|O_EXCL|O_CREAT;
393   unless ($sigfile) {
394     &errmsg ("Unable to open temp file $filename.asc: $!\n");
395     return (255, undef);
396   }
397   if ($pgpstyle eq 'PGP2') {
398     print $sigfile "-----BEGIN PGP MESSAGE-----\n";
399   } else {
400     print $sigfile "-----BEGIN PGP SIGNATURE-----\n";
401   }
402   if (defined $version) {
403     print $sigfile "Version: $version\n";
404   } elsif ($pgpstyle ne 'GPG') {
405     print $sigfile "Comment: Use GnuPG; it's better :)\n";
406   }
407   print $sigfile "\n", $signature;
408   if ($pgpstyle eq 'PGP2') {
409     print $sigfile "\n-----END PGP MESSAGE-----\n";
410   } else {
411     print $sigfile "\n-----END PGP SIGNATURE-----\n";
412   }
413   close $sigfile;
414
415   # Signature saved.  Now save the actual message.
416   my $datafile = new FileHandle "$filename", O_WRONLY|O_EXCL|O_CREAT;
417   unless ($datafile) {
418     &errmsg ("Unable to open temp file $filename: $!\n");
419     unlink "$filename.asc";
420     return (255, undef);
421   }
422   print $datafile $message;
423   close $datafile;
424
425   # Figure out what command line we'll be using.
426   my @command;
427   if ($pgpstyle eq 'GPG') {
428     @command = ($gpgv, qw/--quiet --status-fd=1 --logger-fd=1/);
429   } else {
430     @command = ($pgp, '+batchmode', '+language=en');
431   }
432
433   # Now, call PGP to check the signature.  Because we've written
434   # everything out to a file, this is actually fairly simple; all we need
435   # to do is grab stdout.  PGP prints its banner information to stderr, so
436   # just ignore stderr.  Set PGPPATH if desired.
437   #
438   # For GnuPG, use pubring.gpg if an explicit keyring was configured or
439   # found.  Otherwise, use trustedkeys.gpg in the default keyring location
440   # if found and non-zero, or fall back on pubring.gpg.  This is
441   # definitely not the logic that I would use if writing this from
442   # scratch, but it has the most backward compatibility.
443   local $ENV{PGPPATH} = $keyring if ($keyring && $pgpstyle ne 'GPG');
444   if ($pgpstyle eq 'GPG') {
445     if ($keyring) {
446       push (@command, "--keyring=$keyring/pubring.gpg");
447     } else {
448       my $home = $ENV{GNUPGHOME} || $ENV{HOME};
449       $home .= '/.gnupg' if $home;
450       if ($home && ! -s "$home/trustedkeys.gpg" && -f "$home/pubring.gpg") {
451         push (@command, "--keyring=pubring.gpg");
452       }
453     }
454   }
455   push (@command, "$filename.asc");
456   push (@command, $filename);
457   my $input = new FileHandle;
458   my $output = new FileHandle;
459   my $pid = eval { open3 ($input, $output, $output, @command) };
460   if ($@) {
461     &errmsg ($@);
462     &errmsg ("Execution of $command[0] failed.\n");
463     unlink ($filename, "$filename.asc");
464     return (255, undef);
465   }
466   close $input;
467
468   # Check for the message that gives us the key status and return the
469   # appropriate thing to our caller.  This part is a zoo due to all of the
470   # different formats used.  GPG has finally done the right thing and
471   # implemented a separate status stream with parseable data.
472   #
473   # MIT PGP 2.6.2 and PGP 6.5.2:
474   #   Good signature from user "Russ Allbery <rra@stanford.edu>".
475   # ViaCrypt PGP 4.0:
476   #   Good signature from user:  Russ Allbery <rra@stanford.edu>
477   # PGP 5.0:
478   #   Good signature made 1999-02-10 03:29 GMT by key:
479   #     1024 bits, Key ID 0AFC7476, Created 1999-02-10
480   #      "Russ Allbery <rra@stanford.edu>"
481   #
482   # Also, PGP v2 prints out "Bad signature" while PGP v5 uses "BAD
483   # signature", and PGP v6 reverts back to "Bad signature".
484   local $_;
485   local $/ = '';
486   my $signer;
487   my $ok = 255;
488   while (<$output>) {
489     print if $test;
490     if ($pgpstyle eq 'GPG') {
491       if (/\[GNUPG:\]\s+GOODSIG\s+\S+\s+(\S+)/) {
492         $ok = 0;
493         $signer = $1;
494       } elsif (/\[GNUPG:\]\s+NODATA/ || /\[GNUPG:\]\s+UNEXPECTED/) {
495         $ok = 1;
496       } elsif (/\[GNUPG:\]\s+NO_PUBKEY/) {
497         $ok = 2;
498       } elsif (/\[GNUPG:\]\s+BADSIG\s+/) {
499         $ok = 3;
500       }
501     } else {
502       if (/^Good signature from user(?::\s+(.*)|\s+\"(.*)\"\.)$/m) {
503         $signer = $+;
504         $ok = 0;
505         last;
506       } elsif (/^Good signature made .* by key:\n.+\n\s+\"(.*)\"/m) {
507         $signer = $1;
508         $ok = 0;
509         last;
510       } elsif (/^\S+: Good signature from \"(.*)\"/m) {
511         $signer = $1;
512         $ok = 0;
513         last;
514       } elsif (/^(?:\S+: )?Bad signature /im) {
515         $ok = 3;
516         last;
517       }
518     }
519   }
520   close $input;
521   waitpid ($pid, 0);
522   unlink ($filename, "$filename.asc");
523   umask $umask;
524   return ($ok, $signer || '');
525 }
526
527 # Log an error message, attempting syslog first based on $syslog_method
528 # and falling back on stderr.
529 sub errmsg {
530   my ($message) = @_;
531   $message =~ s/\n$//;
532
533   my $date = '';
534   if ($log_date) {
535     $date = strftime ('%Y-%m-%d %T ', localtime);
536   }
537
538   if ($syslog_method && $] >= 5.006) {
539     eval "use Sys::Syslog";
540     $syslog_method = 'internal';
541   }
542
543   if ($syslog_method eq "logger") {
544     my @loggers = ('/usr/ucb/logger', '/usr/bin/logger',
545                    '/usr/local/bin/logger');
546     my $try;
547     foreach $try (@loggers) {
548       if (-x $try) {
549         $syslog_method = $try;
550         last;
551       }
552     }
553     $syslog_method = '' if $syslog_method eq 'logger';
554   }
555
556   if ($syslog_method ne '' && $syslog_method !~ m%/logger$%) {
557     eval "use Sys::Syslog";
558   }
559
560   if ($@ || $syslog_method eq '') {
561     warn $date, "$0: trying to use Perl's syslog: $@\n" if $@;
562     warn $date, $message, "\n";
563     warn $date, "... while processing $messageid\n"
564       if $messageid;
565
566   } else {
567     $message .= " processing $messageid"
568       if $messageid;
569
570     if ($syslog_method =~ m%/logger$%) {
571       unless (system($syslog_method, "-i", "-p",
572                      "$syslog_facility.$syslog_level", $message) == 0) {
573         if ($? >> 8) {
574           warn $date, "$0: $syslog_method exited status ",  $? >>  8, "\n";
575         } else {
576           warn $date, "$0: $syslog_method died on signal ", $? & 255, "\n";
577         }
578         $syslog_method = '';
579         &errmsg($message);
580       }
581
582     } else {
583       # setlogsock arrived in Perl 5.004_03 to enable Sys::Syslog to use a
584       # Unix domain socket to talk to syslogd, which is the only way to do
585       # it when syslog runs with the -l switch.
586       if ($syslog_method eq "unix") {
587         if ($^O eq "dec_osf" && $] >= 5) {
588           eval 'sub Sys::Syslog::_PATH_LOG { "/dev/log" }';
589         }
590         if ($] <= 5.00403 || ! eval "setlogsock('unix')") {
591           warn $date, "$0: cannot use syslog_method 'unix' on this system\n";
592           $syslog_method = '';
593           &errmsg($message);
594           return;
595         }
596       }
597
598       # Unfortunately, there is no way to definitively know in this
599       # program if the message was logged.  I wish there were a way to
600       # send a message to stderr if and only if the syslog attempt failed.
601       &openlog($0, 'pid', $syslog_facility);
602       &syslog($syslog_level, $_[0]);
603       &closelog();
604     }
605   }
606 }
607
608 sub fail {
609   &errmsg($_[0]);
610   exit 255;
611 }
612
613 # Get a lock in essentially the same fashion as INN's shlock.  return 1 on
614 # success, 0 for normal failure, -1 for abnormal failure.  "normal
615 # failure" is that a lock is apparently in use by someone else.
616 sub shlock {
617   my ($file) = @_;
618   my ($ltmp, $pid);
619
620   unless (defined(&ENOENT)) {
621     eval "require POSIX qw(:errno_h)";
622     if ($@) {
623       # values taken from BSD/OS 3.1
624       sub ENOENT {  2 }
625       sub ESRCH  {  3 }
626       sub EEXIST { 17 }
627     }
628   }
629
630   $ltmp = ($file =~ m%(.*/)%)[0] . "shlock$$";
631
632   # This should really attempt to use another temp name.
633   -e $ltmp && (unlink($ltmp) || return -1);
634
635   open(LTMP, ">$ltmp") || return -1;
636   print LTMP "$$\n" || (unlink($ltmp), return -1);
637   close(LTMP) || (unlink($ltmp), return -1);
638
639   if (!link($ltmp, $file)) {
640     if ($! == &EEXIST) {
641       if (open(LOCK, "<$file")) {
642         $pid = <LOCK>;
643         if ($pid =~ /^\d+$/ && (kill(0, $pid) == 1 || $! != &ESRCH)) {
644           unlink($ltmp);
645           return 0;
646         }
647
648         # OK, the pid in the lockfile is not a number or no longer exists.
649         close(LOCK);            # silent failure is ok here
650
651         # Unlink failed.
652         if (unlink($file) != 1 && $! != &ENOENT) {
653           unlink($ltmp);
654           return 0;
655         }
656
657       # Check if open failed for reason other than file no longer present.
658       } elsif ($! != &ENOENT) {
659         unlink($ltmp);
660         return -1;
661       }
662
663       # Either this process unlinked the lockfile because it was bogus, or
664       # between this process's link() and open() the other process holding
665       # the lock unlinked it.  This process can now try to acquire.
666       if (! link($ltmp, $file)) {
667         unlink($ltmp);
668         return $! == &EEXIST ? 0 : -1; # Maybe another proc grabbed the lock.
669       }
670
671     } else {                    # First attempt to link failed.
672       unlink($ltmp);
673       return 0;
674     }
675   }
676   unlink($ltmp);
677   return 1;
678 }
679
680 =head1 NAME
681
682 pgpverify - Cryptographically verify Usenet control messages
683
684 =head1 SYNOPSIS
685
686 B<pgpverify> [B<-test>] < I<message>
687
688 =head1 DESCRIPTION
689
690 The B<pgpverify> program reads (on standard input) a Usenet control
691 message that has been cryptographically signed using the B<signcontrol>
692 program (or some other program that produces a compatible format).
693 B<pgpverify> then uses a PGP implementation to determine who signed the
694 control message.  If the control message has a valid signature,
695 B<pgpverify> prints (to stdout) the user ID of the key that signed the
696 message.  Otherwise, it exits with a non-zero exit status.
697
698 If B<pgpverify> is installed as part of INN, it uses INN's configuration
699 to determine what signature verification program to use, how to log
700 errors, what temporary directory to use, and what keyring to use.
701 Otherwise, all of those parameters can be set by editing the beginning of
702 this script.
703
704 By default, when running as part of INN, B<pgpverify> expects the PGP key
705 ring to be found in I<pathetc>/pgp (as either F<pubring.pgp> or
706 F<pubring.gpg> depending on whether PGP or GnuPG is used to verify
707 signatures).  If that directory doesn't exist, it will fall back on using
708 the default key ring, which is in a F<.pgp> or F<.gnupg> subdirectory of
709 the running user's home directory.
710
711 INN, when using GnuPG, configures B<pgpverify> to use B<gpgv>, which by
712 default expects keys to be in a keyring named F<trustedkeys.gpg>, since it
713 doesn't implement trust checking directly.  B<pgpverify> uses that file if
714 present but falls back to F<pubring.gpg> if it's not found.  This bypasses
715 the trust model for checking keys, but is compatible with the way that
716 B<pgpverify> used to behave.  Of course, if a keyring is found in
717 I<pathetc>/pgp or configured at the top of the script, that overrides all of
718 this behavior.
719
720 =head1 OPTIONS
721
722 The B<-test> flag causes B<pgpverify> to print out the input that it is
723 passing to PGP (which is a reconstructed version of the input that
724 supposedly created the control message) as well as the output from PGP's
725 analysis of the message.
726
727 =head1 EXIT STATUS
728
729 B<pgpverify> may exit with the following statuses:
730
731 =over 4
732
733 =item 0Z<>
734
735 The control message had a good PGP signature.
736
737 =item 1
738
739 The control message had no PGP signature.
740
741 =item 2
742
743 The control message had an unknown PGP signature.
744
745 =item 3
746
747 The control message had a bad PGP signature.
748
749 =item 255
750
751 A problem occurred not directly related to PGP analysis of signature.
752
753 =back
754
755 =head1 ENVIRONMENT
756
757 B<pgpverify> does not modify or otherwise alter the environment before
758 invoking the B<pgp> or B<gpgv> program.  It is the responsibility of the
759 person who installs B<pgpverify> to ensure that when B<pgp> or B<gpgv>
760 runs, it has the ability to locate and read a PGP key file that contains
761 the PGP public keys for the appropriate Usenet hierarchy administrators.
762 B<pgpverify> can be pointed to an appropriate key ring by editing
763 variables at the beginning of this script.
764
765 =head1 NOTES
766
767 Historically, Usenet news server administrators have configured their news
768 servers to automatically honor Usenet control messages based on the
769 originator of the control messages and the hierarchies for which the
770 control messages applied.  For example, in the past, David Lawrence always
771 issued control messages for the S<"Big 8"> hierarchies (comp, humanities,
772 misc, news, rec, sci, soc, talk).  Usenet news administrators would
773 configure their news server software to automatically honor newgroup and
774 rmgroup control messages that originated from David Lawrence and applied
775 to any of the S<Big 8> hierarchies.
776
777 Unfortunately, Usenet news articles (including control messages) are
778 notoriously easy to forge.  Soon, malicious users realized they could
779 create or remove (at least temporarily) any S<Big 8> newsgroup they wanted by
780 simply forging an appropriate control message in David Lawrence's name.
781 As Usenet became more widely used, forgeries became more common.
782
783 The B<pgpverify> program was designed to allow Usenet news administrators
784 to configure their servers to cryptographically verify control messages
785 before automatically acting on them.  Under the B<pgpverify> system, a Usenet
786 hierarchy maintainer creates a PGP public/private key pair and
787 disseminates the public key.  Whenever the hierarchy maintainer issues a
788 control message, he uses the B<signcontrol> program to sign the control
789 message with the PGP private key.  Usenet news administrators configure
790 their news servers to run the B<pgpverify> program on the appropriate
791 control messages, and take action based on the PGP key User ID that signed
792 the control message, not the name and address that appear in the control
793 message's From: or Sender: headers.
794
795 Thus, appropriate use of the B<signcontrol> and B<pgpverify> programs
796 essentially eliminates the possibility of malicious users forging Usenet
797 control messages that sites will act upon, as such users would have to
798 obtain the PGP private key in order to forge a control message that would
799 pass the cryptographic verification step.  If the hierarchy administrators
800 properly protect their PGP private keys, the only way a malicious user
801 could forge a validly-signed control message would be by breaking the
802 public key encryption algorithm, which (at least at this time) is believed
803 to be prohibitively difficult for PGP keys of a sufficient bit length.
804
805 =head1 HISTORY
806
807 B<pgpverify> was written by David C Lawrence <tale@isc.org>.  Manual page
808 provided by James Ralston.  It is currently maintained by Russ Allbery
809 <rra@stanford.edu>.
810
811 =head1 COPYRIGHT AND LICENSE
812
813 David Lawrence wrote:  "Our lawyer told me to include the following.  The
814 upshot of it is that you can use the software for free as much as you
815 like."
816
817 Copyright (c) 1996 UUNET Technologies, Inc.
818 All rights reserved.
819
820 Redistribution and use in source and binary forms, with or without
821 modification, are permitted provided that the following conditions are
822 met:
823
824 =over 4
825
826 =item 1.
827
828 Redistributions of source code must retain the above copyright notice,
829 this list of conditions and the following disclaimer.
830
831 =item 2.
832
833 Redistributions in binary form must reproduce the above copyright notice,
834 this list of conditions and the following disclaimer in the documentation
835 and/or other materials provided with the distribution.
836
837 =item 3.
838
839 All advertising materials mentioning features or use of this software must
840 display the following acknowledgement:
841
842   This product includes software developed by UUNET Technologies, Inc.
843
844 =item 4.
845
846 The name of UUNET Technologies ("UUNET") may not be used to endorse or
847 promote products derived from this software without specific prior written
848 permission.
849
850 =back
851
852 THIS SOFTWARE IS PROVIDED BY UUNET "AS IS" AND ANY EXPRESS OR IMPLIED
853 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
854 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
855 NO EVENT SHALL UUNET BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
856 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
857 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
858 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
859 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
860 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
861 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
862
863 =head1 SEE ALSO
864
865 gpgv(1), pgp(1).
866
867 L<ftp://ftp.isc.org/pub/pgpcontrol/> is where the most recent versions of
868 B<signcontrol> and B<pgpverify> live, along with PGP public keys used for
869 hierarchy administration.
870
871 =cut
872
873 # Local variables:
874 # cperl-indent-level: 2
875 # fill-column: 74
876 # End: