--- premail-0.46.orig/premail +++ premail-0.46/premail @@ -1,11 +1,13 @@ -#!/usr/local/bin/perl +#!/usr/bin/perl +#!/usr/bin/perl -w <--- It's just to buggy for this :-( # # premail, an e-mail privacy package # +use FileHandle; -$version = '0.449'; +$version = '0.46'; -# Copyright 1996 Raph Levien +# Copyright 1996,1997 Raph Levien # All rights reserved. # # This program is free for commercial and non-commercial use as long as @@ -62,6 +64,16 @@ # other packages. This implementation was a non-trivial and unpaid # effort. +# Both of the following is needed to have secure tempfiles. -Joey +use IO::Handle; + +# Added to make PGPPASSFD pipe work with perl-5.6 stk-12/27/00 +#use Fcntl; + +unless (defined(&O_RDWR)) { + require Fcntl; + import Fcntl qw/O_RDWR O_CREAT O_EXCL/; +} # default configuration options @@ -85,18 +97,25 @@ $config{'rlist-valid'} = 300; -$config{'rlist-url'} = 'http://kiwi.cs.berkeley.edu/rlist'; -$config{'pubring-url'} = 'http://kiwi.cs.berkeley.edu/pubring.pgp'; -$config{'type2-list-url'} = 'http://www.jpunix.com/type2.html'; -$config{'pubring-mix-url'} = 'http://www.jpunix.com/pubring.html'; +$config{'rlist-url'} = ''; +$config{'pubring-url'} = ''; +$config{'type2-list-url'} = ''; +$config{'pubring-mix-url'} = ''; $config{'charset'} = 'iso-8859-1'; $config{'encrypt'} = 'yes'; +my @RELAYS; +#@RELAYS = ('anon.lcs.mit.edu'); + # the following config options are for testing only! #$config{'debug'} = 'chvy'; - +$config{'debug'} = ''; # Turns off troublesome warnings -- bfulgham 7/1/99 + # This is a hack, suggested by Ewen McNeill to handle + # some valid error messages that would take too much + # effort to fix ATM. These errors should not impact + # day-to-day use of the program. # Global state %cmdline_configs = (); # config options set from command line @@ -112,6 +131,7 @@ $editfile = ''; # name of file to edit $dashoi = 0; # -oi on cmd line $more_input = 1; +$in_active=0; # IN handler opened $header_sep = ''; $in_body = ''; # the filename of the input message body $prezilla = 0; # special mode for Netscape Navigator 2.1 @@ -308,6 +328,12 @@ if ($1 =~ /^[mwpqe]$/) { push (@sendmail_args, $_); } } elsif (/^\-od(.)$/) { push (@sendmail_args, $_); + } elsif (/^\-[BNRV].+$/) { + push (@sendmail_args, $_); + } elsif (/^\-[BNRV]$/) { + if ($#_ < 0) { &error ("$_ option needs an argument\n"); } + push (@sendmail_args, $_); + push (@sendmail_args, shift); } elsif (/^\-f$/) { if ($#_ < 0) { &error ("$_ option needs an argument\n"); } shift; # discard @@ -363,7 +389,8 @@ &apply_cmdline_configs (); if ($config{'preferences'}) { $preferences = &tilde_expand ($config{'preferences'}); - open (PREF, $preferences); + + if (open (PREF, $preferences)) { while () { if (/^\s*\$config\{\"([^\"]+)\"\}\s*\=\s*\"([^\"]*)\"/ || /^\s*\$config\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { @@ -371,20 +398,29 @@ } } close (PREF); + } } &apply_cmdline_configs (); if ($config{'addresses'}) { - open (ADDR, &tilde_expand ($config{'addresses'})); - while () { - if (/^([\w\-\_\+\.\@\!]+)\:\s*(.*)$/) { - $recip = &strip_address ($1); - $alias{$recip} = $2; - } + + # 7-3-98 BAF -- Check file was opened before using it + if (open (ADDR, &tilde_expand ($config{'addresses'}))) { + while () { + if (/^([\w\-\_\+\.\@\!]+)\:\s*(.*)$/) { + $recip = &strip_address ($1); + $alias{$recip} = $2; + } + } + close (ADDR); + } else { + warn "Note: Assuming you don't want to use an address file.\n"; } - close (ADDR); + } if ($config{'logfile'}) { - open (LOG, '>>'.&tilde_expand_mkdir ($config{'logfile'})); + # 7-3-98 BAF -- Check file was opened before using it + open (LOG, '>>'.&tilde_expand_mkdir ($config{'logfile'})) || + die "Error: Can't open logfile for writing: $!"; } foreach (keys %config) { &pdv ("\$config\{\'$_\'\} = \'$config{$_}\'\;\n"); @@ -413,6 +449,7 @@ if (!open (IN, $editfile)) { &error ("cannot open edit file $editfile\n"); } + $in_active = 1; return 1; } elsif ($dashbs) { # do simple SMTP @@ -463,7 +500,9 @@ @in_headers = (); for ($lineno = 0;;$lineno++) { - $line = &get_line_body ($body); + if (! defined($line = &get_line_body ($body))) { + last; + } if ($handle_from && $lineno == 0 && $line =~ /^From /) { return $line; } @@ -495,16 +534,16 @@ my $line; if ($edit || $post) { - $line = ; + $line = if ($in_active); } elsif ($dashbs) { $line = ; - if ($line eq ".\n") { return undef; } + if (!defined $line || $line eq ".\n") { return undef; } $line =~ s/^\.\./\./; } else { $line = ; if (!defined $line || !$dashoi && $line eq ".\n") { return undef; } } - $line =~ s/\r$//; + $line =~ s/\r$// if defined $line; return $line; } @@ -516,6 +555,7 @@ # } if ($edit || $post) { close (IN); + $in_active = 0; } elsif ($dashbs) { print "250 Message accepted for delivery\n"; $more_input = 1; @@ -530,7 +570,8 @@ if ($body eq '-' && $n > 1) { $new_body = &tmp_filename (); - open (TMP, '>'.$new_body); + open (TMP, '>'.$new_body) || + die "Error: Cannot create temporary files: $!"; &open_body ($body); while ($line = &get_line_body ($body)) { print TMP $line; @@ -1045,7 +1086,8 @@ ."\n ".'protocol="application/pgp-encrypted"'."\n"); $body = $new_body; $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; print NEW "This message is in PGP/MIME format, according to the" ." Internet Draft\n"; print NEW "draft-elkins-pem-pgp-04.txt. For more information, see:\n"; @@ -1157,7 +1199,8 @@ 'Content-Type: multipart/encrypted; boundary="'.$boundary.'";' ."\n ".'protocol="application/moss-keys"'."\n"); $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; print NEW "--$boundary\n"; print NEW "Content-Type: application/moss-keys\n"; print NEW "Content-Transfer-Encoding: quoted-printable\n"; @@ -1209,7 +1252,8 @@ $invoc .= ' > '.$errfile.' 2>&1'; open (MOSS, "|$invoc"); $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; $boundary = &random (80); push (@deliver_headers, 'MIME-Version: 1.0'."\n", @@ -1268,11 +1312,11 @@ my ($body, @the_recips) = @_; my ($key_type, $key); my (@keys); - my ($new_body, $err, $line); + my ($err, $line); my (@mime_fields, $prefix, $boundary); my ($sign_type, $sign); my ($invoc, $errfile); - my ($in_body, $sig_body, $new_body); + my ($in_body, $sig_body); my ($user); @keys = (); @@ -1315,7 +1359,8 @@ if ($status) { &error ("RIPEM error\n$err"); } &pdv ($err); $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; $boundary = &random (80); push (@deliver_headers, 'MIME-Version: 1.0'."\n", @@ -1354,11 +1399,11 @@ my ($body, @the_recips) = @_; my ($key_type, $key); my (@keys); - my ($new_body, $err, $line); + my ($err, $line); my (@mime_fields, $prefix); my ($sign_type, $sign); my ($invoc, $errfile); - my ($in_body, $new_body); + my ($in_body); my ($user); @keys = (); @@ -1431,7 +1476,8 @@ my ($new_body); $new_body = &tmp_filename (); - open (FORCE, '>'.$new_body); + open (FORCE, '>'.$new_body) || + die "Couldn't open file: $!"; print FORCE &canonicalize_line_enc ($prefix); &open_body ($body); while (defined ($line = &get_line_body ($body))) { @@ -1449,7 +1495,8 @@ if ($body ne '-') { return $body; } $new_body = &tmp_filename (); - open (FORCE, '>'.$new_body); + open (FORCE, '>'.$new_body) || + die "Couldn't open file: $!"; &open_body ($body); while (defined ($line = &get_line_body ($body))) { print FORCE $line; @@ -1487,6 +1534,16 @@ } else { $num_shuf = 3; } + if ($config{"reliability-threshold"}) { + $rel_thres = $config{"reliability-threshold"}; + } else { + $rel_thres = 100; + } + if ($config{"latency-threshold"}) { + $lat_thres = $config{"latency-threshold"}; + } else { + $lat_thres = 0; + } foreach $hop (@chain) { if ($hop =~ /^\d+$/) { for ($i = 0; $i < $hop; $i++) { @@ -1498,8 +1555,14 @@ || &member ('eric', @options))) { next; } - $score = $reliability{$remailer}; - $score -= $latency{$remailer} * 1e-5; + if ($reliability{$remailer}>=$rel_thres ) { + $score = 100; + } else { + $score = $reliability{$remailer}; + } + if ($latency{$remailer}>=$lat_thres) { + $score -= $latency{$remailer} * 1e-5; + } if ($config{'encrypt'} && (&member ('pgp', @options) || &member ('pgp.', @options))) { @@ -1511,7 +1574,8 @@ || &member ('pgponly', @options)) { next; } if ($config{'no-middle'} && &member ('middle', @options)) { next; } - if (&member ('reord', @options)) { $score += 0.1; } + if (&member ('reord', @options) + && $rel_thres==100) { $score += 0.1; } if (&member ('filter', @options)) { $score -= 10; } if (&member ('mon', @options)) { $score -= 10; } if ($#new_chain < 0 && !$erb @@ -1593,7 +1657,8 @@ &getfile_from_web_html (&tilde_expand_mkdir ($config{'pubring'}), $config{'pubring-url'}); } - open (REMAILERS, $remailers_file); + open (REMAILERS, $remailers_file) || + die "Configuration error: Can't open Remailers file: $!"; while () { if (/^\s*\$remailer\{\"([^\"]+)\"\}\s*\=\s*\"([^\"]*)\"/ || /^\s*\$remailer\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { @@ -1635,7 +1700,8 @@ my ($file, $url) = @_; if (&open_web ($url)) { - open (PUT, '>'.$file); + open (PUT, '>'.$file) || + die "Couldn't open file: $!"; while () { print PUT; } @@ -1662,7 +1728,8 @@ if (&open_web ($url)) { while () { if (!$yup && !$inpre && /^\s*\\s*$/i) { - open (PUT, '>'.$file); + open (PUT, '>'.$file) || + die "Couldn't open file: $!"; $put_open = 1; $inpre = 1; } elsif ($inpre && /^\s*\<\/pre\>\s*$/i) { @@ -1678,7 +1745,8 @@ } else { push (@window, $_); if ($#window + 1 == 5) { - open (PUT, '>'.$file); + open (PUT, '>'.$file) || + die "Couldn't open file: $!"; $put_open = 1; print PUT @window; $yup = 1; @@ -1687,7 +1755,7 @@ } } if ($put_open) { close (PUT); } - close (GET); + close (WWW); } } @@ -1715,7 +1783,8 @@ if (!-e $type2_list) { &error ("Cannot find type2.list; not at $type2_list\n"); } - open (LIST, "$type2_list"); + open (LIST, "$type2_list") || + die "Couldn't open file: $!"; $num = 0; while () { if (/^(\S+)\s+(\S+)\s/) { @@ -1817,7 +1886,7 @@ } } push (@deliver_headers, "To\: $new_to\n"); - if ($addl =~ /\.(encrypt\-key\:\s*[^\.]+)(\.|$)/i) { + if (defined $addl && $addl =~ /\.(encrypt\-key\:\s*[^\.]+)(\.|$)/i) { $hash = "$1\n".$hash; $body = &cat_tail ($body, "\*\*\n"); } @@ -1867,7 +1936,8 @@ my ($outfile, $line); $outfile = &tmp_filename (); - open (OUT, '>'.$outfile); + open (OUT, '>'.$outfile) || + die "Couldn't open file: $!"; open_body ($body); while (defined ($line = &get_line_body ($body))) { print OUT $line; @@ -2209,12 +2279,13 @@ } else { $tmpfile = &tmp_filename (); } - open (DELIVER, '>'.$tmpfile); + open (DELIVER, '>'.$tmpfile) || + die "Couldn't open file: $!"; } else { # we know it's sendmail $invoc = &bin_sendmail (); if ($#sendmail_args >= 0) { - $invoc .= ' '.join (' ', $sendmail_args); + $invoc .= ' '.join (' ', @sendmail_args); } $invoc .= ' -oi'; foreach $recip (@the_recips) { @@ -2226,11 +2297,13 @@ $invoc .= ' << -eof-'; if (!$deliver_debug) { open (DELIVER, '>>' - .&tilde_expand_mkdir ($config{'storefile'})); + .&tilde_expand_mkdir ($config{'storefile'})) || + die "Couldn't open file: $!"; } &deliver_line ($invoc."\n"); } else { - open (DELIVER, '|'.$invoc); + open (DELIVER, '|'.$invoc) || + die "Couldn't open file: $!"; } } foreach (@deliver_headers) { @@ -2252,16 +2325,19 @@ &close_body ($body); if ($post) { close (DELIVER); - $post = &tilde_expand ($config{'post'}); - if ($post eq '') { - $post = "/usr/lib/mh/post"; + my $ppost = &tilde_expand ($config{'post'}); + # bfulgham 7/1/99 -- another EDM fix, checks for undefined + # value on return + if (!defined($ppost) || $ppost eq '') { + $ppost = "/usr/lib/mh/post"; } - system ($post, @post_args, $tmpfile); + system ($ppost, @post_args, $tmpfile); unlink $tmpfile; } elsif ($edit && !$prezilla) { close (DELIVER); if ($editfile eq '-') { - open (CAT, $tmpfile); + open (CAT, $tmpfile) || + die "Couldn't open file: $!"; while () { print; } close (CAT); &delete_tmpfile ($tmpfile); @@ -2388,9 +2464,13 @@ } else { $addr .= $token; } } - $addr =~ s/^\s+//s; - $addr =~ s/\s+$//s; - if ($addr ne '') { push (@addrs, $addr); } + # bfulgham, 7-1-99: EDM suggestion -- check for + # a defined $addr before manipulating + if (defined($addr)) { + $addr =~ s/^\s+//s; + $addr =~ s/\s+$//s; + if ($addr ne '') { push (@addrs, $addr); } + } return (@addrs); } @@ -2441,6 +2521,7 @@ } $strip =~ s/^\s+//s; $strip =~ s/\s+$//s; + return ($strip, '') if ($config{'no-caret'}); return ($strip, $caret); } @@ -2639,7 +2720,8 @@ "Mime-Version: 1.0\n", "Content-Type: multipart/mixed; boundary=\"_\"\n"); $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; print NEW "--_\n"; print NEW "\n"; print NEW $error_msg; @@ -2670,7 +2752,8 @@ print STDERR $error_msg; $dead_letter = &tilde_expand ($config{'dead-letter'}); print STDERR "Saving message in $dead_letter\n"; - open (DEAD, '>>'.$dead_letter); + open (DEAD, '>>'.$dead_letter) || + die "Couldn't open a dead letter file: $!"; print DEAD (("From $ENV{'USER'} ".localtime)."\n"); foreach $line (@in_headers) { print DEAD $line; @@ -2801,6 +2884,7 @@ # (@new_dict) = &delete_field ($key, @dict) my ($key, @dict) = @_; my (@new_dict); + my ($field_key, $field_val); @new_dict = (); foreach $field (@dict) { @@ -2843,6 +2927,10 @@ # Expand filenames of the form ~/file. Also expand $< sequence (uid). my ($file_name) = @_; + # bfulgham, 7-1-99: Another EDM fix. If file_name is undefined, + # return immediately to avoid error messages. + return $file_name unless defined($file_name); + if ($file_name =~ /^\~[^\/]/) { &error ("premail can't handle ~user/ form in $file_name, use ~/ or\n". "full path name instead\n"); @@ -2917,16 +3005,22 @@ # Return the name for a new temp file (and add to @open_tmpfiles). # Reference count is set to one. my ($suffix) = @_; + my $base; my $fn; $tmpfile_count++; - $fn = &tilde_expand ($config{'tmpdir'}); - $fn =~ s/([^\/])$/$1\//; - $fn .= 'premail.'.$$.'.'.$tmpfile_count; + $base = &tilde_expand ($config{'tmpdir'}); + $base =~ s/([^\/])$/$1\//; + $base .= 'premail.'.$$.'.'; + $fn = $base . $tmpfile_count; $fn .= $suffix if $suffix; -# Dangerous: this next command assumes Unix file deletion semantics. It -# was not present in 0.44 and, I believe, can be safely removed. - unlink ($fn); + while (!sysopen(TMPFH,$fn,&O_RDWR|&O_CREAT|&O_EXCL,0600) && $tmpfile_count < 32000) { + $tmpfile_count++; + $fn = $base . $tmpfile_count; + $fn .= $suffix if $suffix; + } + die "Can't open temp file: $!\n" if ($tmpfile_count >= 32000); + close (TMPFH); push (@open_tmpfiles, $fn); $tmpfile_refcnt{$fn} = 1; return $fn; @@ -2972,8 +3066,8 @@ $data = ''; if (open (ERRFILE, $file)) { - print $_; while () { + # print $_; # Removed 9-7-1999 BFulgham to allow filter use $data .= $_; } close (ERRFILE); @@ -2989,13 +3083,13 @@ my ($file) = @_; my ($c); - open (F, $file); + open (F, $file) || die "Can't open $file: $!"; seek (F, (-s $file) - 1, 0); sysread (F, $c, 1); close (F); # print "Trailing character is really ".unpack ('c', $c)."\n"; if ($c ne "\n") { - open (F, '>>'.$file); + open (F, '>>'.$file) || die "Can't open file: $!"; print F "\n"; close F; } @@ -3015,6 +3109,7 @@ # this interface). # # $err is the string returned. + print "I'm going into pgp now\n"; my ($body, $prefix, $sign, $signuser, $pubring, @keys) = @_; my ($outfile, $errfile); my ($invoc, $status, $line, $pass, $pr, $sr); @@ -3040,7 +3135,9 @@ $pass = ''; } if ($pubring) { $invoc .= ' +pubring='.&shell_quote ($pubring); } - $invoc .= ' +comment= -feat'; + + # TEMP FIX -- Language support. Will be updated for others + $invoc .= ' +language=en +comment= -feat'; if ($sign) { $invoc .= 's -u '.&shell_quote ($signuser); &load_secrets (); @@ -3083,7 +3180,7 @@ my ($outfile, $keys, $line); $outfile = &tmp_filename (); - open (OUT, '>'.$outfile); + open (OUT, '>'.$outfile) || die "Couldn't open file: $!"; if ($sign) { $sign = " (sign $signuser)"; } @@ -3126,7 +3223,9 @@ $signuser = '0x'; $pass = ''; } - $invoc .= ' +comment= -fats +clearsig=on'; + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +comment= -fats +clearsig=on'; $invoc .= ' -u '.&shell_quote ($signuser); unless (defined $pass) { if (defined $pgppass{$signuser}) { @@ -3162,6 +3261,7 @@ # for obtaining the password lies below this interface). # # $err is the string returned. + print "I'm using the mime-pgp signing routine.\n"; my ($body, $prefix, $signuser, $sign_type) = @_; my ($outfile, $errfile, $mimefile); my ($invoc, $status, $line, $pass, $boundary); @@ -3182,7 +3282,9 @@ $signuser = '0x'; $pass = ''; } - $invoc .= ' +comment= -fabst'; + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +comment= -fabst'; $invoc .= ' -u '.&shell_quote ($signuser); unless (defined $pass) { if (defined $pgppass{$signuser}) { @@ -3197,7 +3299,7 @@ $status = &open_pgp ($invoc, $pass, 'w'); if (!$status) { &error ("Error invoking PGP!\n"); } &open_body ($body); - open (NEW, '>'.$mimefile); + open (NEW, '>'.$mimefile) || die "Couldn't open file: $!"; print NEW "This message is in PGP/MIME format, according to the" ." Internet Draft\n"; print NEW "draft-elkins-pem-pgp-04.txt. For more information, see:\n"; @@ -3238,7 +3340,7 @@ return ($mimefile, $err, $boundary); } -my $PUBRING, $SECRING; +my ($PUBRING, $SECRING); sub pgp_decrypt { # ($out_body, $err) = &pgp_decrypt ($body, $pass) # Try to decrypt $body using passphrase $pass. $out_body is null on error. @@ -3251,7 +3353,9 @@ $outfile = &tmp_filename (); $errfile = &tmp_filename (); $invoc = &tilde_expand ($config{'pgp'}); - $invoc .= ' +batchmode=on'; + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode=on'; $invoc .= " +pubring=$PUBRING" if $PUBRING; $invoc .= " +secring=$SECRING" if $SECRING; # if ($pass =~ /^RING$;/) { @@ -3299,16 +3403,18 @@ $errfile = &tmp_filename (); $invoc = &tilde_expand ($config{'pgp'}); - $invoc .= ' +batchmode=on '; + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode=on '; $invoc .= ' '.$pgp_file; $invoc .= ' '.$signed_file; $invoc .= ' > '.$errfile.' 2>&1'; &pdv ("Invoking PGP as $invoc\n"); $status = &open_pgp ($invoc, '', ''); $err = &read_and_delete ($errfile); - if (!$status) { - &error ("Error in PGP verification!\n$err"); - } +# if (!$status) { +# &error ("Error in PGP verification!\n$err"); +# } &pdv ($err); return ($err); } @@ -3329,6 +3435,7 @@ # special PGP temp subdirectory, on a per-process basis (this assumes # that each process invokes only one PGP at a time, which is safe given # the relentless file-file orientation of this version of premail). + my ($invoc, $pass, $mode) = @_; if ($mode eq 'r') { $invoc = $invoc.'|'; } @@ -3344,6 +3451,8 @@ $ENV{'TMP'} = $pgp_tmpdir; if ($pass) { pipe (READER, WRITER); + # Added to make PGPPASSFD pipe work with perl-5.6 stk-12/27/00 + fcntl(READER, F_SETFD, 0) or die "Can't fcntl: $!\n"; $ENV{'PGPPASSFD'} = fileno(READER); } $status = open (PGP, $invoc); @@ -3390,7 +3499,9 @@ $chars_needed = 2 + sprintf ("%d", $bits / 8); &pdv ($config{'pgp'}." +makerandom=$chars_needed $outf" ." >/dev/null 2>&1\n"); - $status = system $config{'pgp'}." +makerandom=$chars_needed $outf" + + # TEMP FIX for language -- will be updated + $status = system $config{'pgp'}." +language=en +makerandom=$chars_needed $outf" ." >/dev/null 2>&1"; &pdv ($status."\n"); if (!$status) { @@ -3416,7 +3527,7 @@ ."generate randomness!\n"); } $inf = &tmp_filename (); - open (INF, '>'.$inf); + open (INF, '>'.$inf) || die "Couldn't open file: $!"; for ($i = 0; $i < 256; $i++) { print INF (rand ())."\n"; } @@ -3425,7 +3536,7 @@ ($inf, '', '', '', '', $config{'signuser'}); print "$outf\n"; &delete_tmpfile ($inf); - open (OUTF, $outf); + open (OUTF, $outf) || die "Couldn't open output file: $!"; @window = (); while () { if (/^[A-Za-z0-9\+\/]/) { push (@window, $_); } @@ -3449,6 +3560,7 @@ # This routine needs to do a lot more. # # Sets the global variables $secrets_loaded and $premail_secrets + my ($ps_pgp); if (!defined $secrets_loaded) { @@ -3458,7 +3570,8 @@ &do_login (!$interactive); } if (-e $premail_secrets) { - open (SECRETS, $premail_secrets); + open (SECRETS, $premail_secrets) || + die "Couldn't open secrets file: $!"; while () { if (/^\s*\$pgppass\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { $pgppass{$1} = $2; @@ -3498,9 +3611,15 @@ &error ("Need to log in to access secrets\n"); } if (!-e $premail_secrets) { - open (TOUCH, '>'.$premail_secrets); + if (!sysopen(TOUCH,$premail_secrets,&O_WRONLY|&O_CREAT|&O_EXCL,0600)) { + &error ("Can't open secrets file for writing\n"); + } &pfi ("Creating secrets file $premail_secrets\n"); close (TOUCH); + } else { + if (!-o $premail_secrets) { + &error ("Secrets file owned by wrong user.\n"); + } } $secret_backup = $premail_secrets.'~'; rename ($premail_secrets, $secret_backup); @@ -3586,10 +3705,18 @@ sub getpass { # $pass = &getpass ($x) # Get the premail passphrase, either from X or from stdin. + my ($x) = @_; my ($pass); if ($x) { + # if ($ENV{'DISPLAY'}) { + # require Gtk; + # $pass = create_entry(); + # } + + # This doesn't seem to work right with Debian's latest + # security fixes. Above is a Gtk interface. if ($ENV{'DISPLAY'}) { pipe (READER, WRITER); system 'xterm -geometry 42x4-5-5 -e perl -e \'' @@ -3615,7 +3742,8 @@ print "\n"; system "stty echo"; } - chop $pass; + # We might not always have a newline -- use chomp! + chomp $pass; return $pass; } @@ -3626,11 +3754,16 @@ $errfile = &tmp_filename (); $invoc = &tilde_expand ($config{'pgp'}); - $invoc .= ' +batchmode=on -f'; + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode=on -f'; $invoc .= ' < '.$ps_pgp; $invoc .= ' > '.$ps; $invoc .= ' 2> '.$errfile; &pdv ("Invoking PGP as $invoc\n"); + if(-e $ps) { + &error ("Premail secrets file already exists\n"); + } $status = &open_pgp ($invoc, $pass, ''); $err = &read_and_delete ($errfile); &pdv ($err); @@ -3910,7 +4043,7 @@ &replace_field ('Content-Type: text/plain; charset=' .$config{'charset'}."\n", @deliver_headers); - } elsif ($charset =~ /^iso-8859-\d$/i && !$non_ascii) { + } elsif (($charset =~ /^iso-8859-\d$/i || $charset =~ /^koi8-r$/i) && !$non_ascii) { # Should we detect other charsets which are supersets of us-ascii? if (!$mv_present) { push (@deliver_headers, 'MIME-Version: 1.0'."\n"); @@ -3923,7 +4056,7 @@ } } # must deal with existing cte, charset, etc. - if ($non_ascii || $ctrl || $other) { + if ((($non_ascii || $ctrl) && (!$cte_present || lc $cte_val ne '8bit')) || $other) { # Do the QP &pdv ("Doing QP encoding!\n"); if (!$mv_present) { @@ -3933,7 +4066,7 @@ &replace_field ('Content-Transfer-Encoding: quoted-printable'."\n", @deliver_headers); $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || die "Couldn't open file: $!"; &open_body ($body); while (defined ($line = &get_line_body ($body))) { print NEW &encode_qp ($line, $type); @@ -4000,7 +4133,7 @@ my ($val, $present, $param_val); my ($type_base, @type_params); - open (MNBIN, $infile); + open (MNBIN, $infile) || die "Couldn't open file: $!"; $newfile = ''; @sepstack = (); $blocksize = 1024; @@ -4073,7 +4206,7 @@ } elsif ($newfile eq '') { $newfile = &tmp_filename (); # print STDERR "newfile = $newfile\n"; - open (MNBOUT, '>'.$newfile); + open (MNBOUT, '>'.$newfile) || die "Couldn't open file: $!"; } print MNBOUT (join ('', @header)); } elsif ($eof) { @@ -4210,8 +4343,8 @@ print "\n"; print " premail -ripemkey\n"; print " Generate S/MIME key\n"; - print "\n"; - print "Please see http://www.c2.net/~raph/premail/ for more info.\n"; + #print "\n"; + #print "Please see http://www.c2.net/~raph/premail/ for more info.\n"; exit 0; } @@ -4225,7 +4358,7 @@ close (WWW); if ($pubring ne '') { $pubring_fn = &tilde_expand_mkdir ($config{'pubring'}); - open (PUB, '>'.$pubring_fn); + open (PUB, '>'.$pubring_fn) || die "Couldn't open file: $!"; print PUB $pubring; close (PUB); } @@ -4291,7 +4424,7 @@ print $line; $state = 0; $msg_body = &tmp_filename (); - open (MSG, '>'.$msg_body); + open (MSG, '>'.$msg_body) || die "Couldn't open file: $!"; while (defined ($line = &get_line ())) { if ($line =~ /^From / && $state == 1) { close (MSG); @@ -4300,7 +4433,7 @@ print $line; push (@open_tmpfiles, $msg_body); $tmpfile_refcnt{$msg_body} = 1; - open (MSG, '>'.$msg_body); + open (MSG, '>'.$msg_body) || die "Couldn't open file: $!"; $state = 0; } elsif ($state == 0 && $line eq "\n") { $state = 1; @@ -4336,6 +4469,8 @@ exit 0; } +use vars qw($SAVE_BODY); + sub decode_msg { # &decode_msg ($msg) # This is possibly the ugliest function in all of premail. Most of it is @@ -4350,12 +4485,12 @@ my ($msg_body, $new_msg, $save_select); if ($msg ne '-') { - open (SAVE_BODY, "<&BODY"); &open_body ($msg); + open (SAVE_BODY, "<&BODY") || die "Can't open a save file: $!"; } &get_header ($msg); $msg_body = &tmp_filename (); - open (MSG_BODY, '>'.$msg_body); + open (MSG_BODY, '>'.$msg_body) || die "Can't open a message file: $!"; while (defined ($line = &get_line_body ($msg))) { print MSG_BODY $line; } @@ -4372,7 +4507,7 @@ } @deliver_headers = @new_headers; $new_msg = &tmp_filename (); - open (NEW_MSG, '>'.$new_msg); + open (NEW_MSG, '>'.$new_msg) || die "Couldn't open file: $!"; $save_select = select NEW_MSG; select NEW_MSG; &decode_body ($msg_body, '', 0); @@ -4387,7 +4522,7 @@ &close_body ($new_msg); if ($msg ne '-') { &close_body ($msg); - open (BODY, "<&SAVE_BODY"); + open (BODY, "<&SAVE_BODY") || die "Couldn't open file: $!"; } } @@ -4456,7 +4591,7 @@ $encrypted = 1; } $pgp_body = &tmp_filename (); - open (DEC, '>'.$pgp_body); + open (DEC, '>'.$pgp_body) || die "Couldn't open file: $!"; $body_open = 1; foreach $l (@window) { print DEC $l; @@ -4767,7 +4902,7 @@ || $protocol eq 'application/x-pkcs7-signature' || $protocol eq 'application/pkcs7-signature')){ $body[$part] = &tmp_filename (); - open (NEW, '>'.$body[$part]); + open (NEW, '>'.$body[$part]) || die "Couldn't open file: $!"; $body_open = 1; $state = 1; $canon = ($protocol eq 'application/pgp-signature' @@ -4779,13 +4914,16 @@ || $part == 2) { $body[$part] = &tmp_filename (); if ($cte eq '' || &mossbin('mossdecode', 1) eq '') { - open (NEW, '>'.$body[$part]); + open (NEW, '>'.$body[$part]) || + die "Couldn't open file: $!"; } elsif ($cte eq 'quoted-printable') { open (NEW, '|'.&mossbin ('mossdecode') - .' -qp > '.$body[$part]); + .' -qp > '.$body[$part]) || + die "Couldn't open file: $!"; } elsif ($cte eq 'base64') { open (NEW, '|'.&mossbin ('mossdecode') - .' -b64 > '.$body[$part]); + .' -b64 > '.$body[$part]) || + die "Couldn't open file: $!"; } else { &error ("Unknown Content-Transfer-Encoding: $cte\n"); } @@ -4878,7 +5016,7 @@ $| = 1; $new_body = &tmp_filename (); - open (NEW, '>'.$new_body); + open (NEW, '>'.$new_body) || die "Couldn't open file: $!"; &open_body ($body); $state = 0; while (defined ($line = &get_line_body ($body))) { @@ -5046,7 +5184,9 @@ $movemail = &tilde_expand ($config{'movemail'}); $status = system "$movemail $in $out"; if ($status) { exit $status >> 8; } - open (MOVE_OUT, '>'.$move_work_fn); + if (!sysopen(MOVE_OUT,$move_work_fn,&O_WRONLY|&O_CREAT|&O_EXCL,0600)) { + &error ("Can't open $move_work_fn for writing\n"); + } select MOVE_OUT; &decode ($out); } @@ -5129,7 +5269,7 @@ &load_secrets (); foreach (keys %pgpring) { my ($tpr, $tsr) = &makerings ($pgpring{$_}); - system ("$PGP +batchmode +verbose=0 -kx 0x $pr $tpr > /dev/null"); + system ("$PGP +language=en +batchmode +verbose=0 -kx 0x $pr $tpr > /dev/null"); #filecat ($tpr, $pr); filecat ($tsr, $sr); &delete_tmpfile ($tpr); @@ -5157,12 +5297,14 @@ # &pdv ('&makerings ("'.join ('", "', @_)."\")\n"); foreach ([$pr, $pk], [$sr, $sk]) { open TMP, ">$$_[0]"; - print TMP $$_[1]; + print TMP $$_[1] if defined $$_[1]; close TMP; } my $PGP = &tilde_expand ($config{'pgp'}); foreach $id (@pubkeys) { - my $invoc = "$PGP +batchmode +force +verbose=0 -kx " + + # TEMP FIX for lanuage -- will be updated + my $invoc = "$PGP +language=en +batchmode +force +verbose=0 -kx " . "$id $pr $pubring 2>&1"; &pdv ("$invoc > /dev/null\n"); system "$invoc > /dev/null"; @@ -5189,7 +5331,9 @@ $outfile = &tmp_filename (); $errfile = &tmp_filename (); $invoc = &tilde_expand ($config{'pgp'}); - $invoc .= ' +batchmode +force +verbose=0 '; + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode +force +verbose=0 '; $invoc .= " +pubring=$pr +secring=$sr "; $invoc .= $cmd; $invoc .= ' < ' . $infile if $infile; @@ -5248,7 +5392,8 @@ EOF ; - if (system ("$PGP -kg +pubring=$pr +secring=$sr +verbose=0")) { + # TEMP FIX for language -- will be updated + if (system ("$PGP +language=en -kg +pubring=$pr +secring=$sr +verbose=0")) { print STDERR "\nKey generation failed.\n"; &killbaks ($pr, $sr); &delete_open_tmpfiles (); @@ -5272,7 +5417,9 @@ foreach $a ("$kid $pr", "$remid $pr " . &tilde_expand ($config{'pubring'}), "$kid $sr $defsr") { - my $invoc = "$PGP +batchmode +force +verbose=0 -kx $a 2>&1"; + + # TEMP FIX for language -- will be updated + my $invoc = "$PGP +language=en +batchmode +force +verbose=0 -kx $a 2>&1"; # print STDERR "+ $invoc\n"; my $result = `$invoc`; unless ($result =~ /^Key extracted/m) { @@ -5300,7 +5447,8 @@ EOF ; - my $invoc = "$PGP +secring=$sr -ke $kid $pr"; + # TEMP FIX for language -- will be updated + my $invoc = "$PGP +language=en +secring=$sr -ke $kid $pr"; # print STDERR "+ $invoc\n"; if (system ($invoc)) { print STDERR "Edit failed.\n"; @@ -5495,6 +5643,7 @@ } } } + $signsend = 'n' unless $signsend; if ($#args >= 1) { $to = $args[1]; } elsif ($#args < 0) { @@ -5542,7 +5691,7 @@ if ($to eq 'delete') { $prefix .= 'New-Password:'."\n\n"; $replyblock_fn = &tmp_filename (); - open (TMP, '>'.$replyblock_fn); + open (TMP, '>'.$replyblock_fn) || die "Couldn't open file: $!"; close (TMP); } else { $prefix .= 'Reply-Block:'."\n"; @@ -5609,7 +5758,6 @@ $fullname = &query ('Full name of pseudonym (not just ' . 'E-mail address)', $fullname); $fullname =~ s/[\'\^\n]//g; # kludge for secrets file - $signsend = 'n' unless $signsend; $signsend = &query ('Sign mail with (R)emailer key, ' . '(P)seudonym key or (N)o key?', $signsend); @@ -5629,7 +5777,7 @@ $prefix .= join ('', @rbs); } $replyblock_fn = &tmp_filename (); - open (TMP, '>'.$replyblock_fn); + open (TMP, '>'.$replyblock_fn) || die "Couldn't open file: $!"; close (TMP); } # print $prefix; @@ -5731,7 +5879,7 @@ my ($replyblock_fn); $replyblock_fn = &tmp_filename (); - open (REPLY, '>'.$replyblock_fn); + open (REPLY, '>'.$replyblock_fn) || die "Couldn't open file: $!"; print REPLY "To: $to\n"; print REPLY "Chain: $chain \n" if $chain; print REPLY "\n"; @@ -5771,6 +5919,7 @@ if (!open (IN, $body)) { &error ("Internal error opening replyblock\n"); } + $in_active = 1; @in_headers = ("To: $to\n"); push (@in_headers, "Chain: $chain\n") if $chain; $header_sep = "\n"; @@ -5784,6 +5933,7 @@ } &send_group ($groups[0]); close (IN); + $in_active = 0; } sub find_nym { @@ -5814,7 +5964,7 @@ if ($all || $test eq 'ek') { $replyblock_fn = &make_reply_block ($target, $remailer.'.Encrypt-Key: test'); - open (RB, ">>$replyblock_fn"); + open (RB, ">>$replyblock_fn") || die "Couldn't open file: $!"; print RB "Test of ek functionality of $remailer." ." This line must be encrypted.\n"; # print RB "**\n"; @@ -5897,8 +6047,8 @@ my ($file1, $file2) = @_; my ($l2); - open (F1, $file1); - open (F2, $file2); + open (F1, $file1) || die "Couldn't open F1: $!"; + open (F2, $file2) || die "Couldn't open F2: $!"; while () { $l2 = ; if ($_ ne $l2) { close (F1); close (F2); return 1; } @@ -5997,7 +6147,7 @@ # The main loop $quit = 0; - $inoef = 0; + $ineof = 0; while (!$quit) { $rin = $win = $ein = ''; vec ($rin, fileno(STDIN), 1) = 1 unless $ineof; @@ -6315,9 +6465,9 @@ # Open a Web connection for the file as file handle WWW. my ($url) = @_; my ($host, $port, $suf); - my ($fqdn, $aliases, $type, $len, $thataddr); + my ($fqdn, $aliases, $type, $len); my ($name, $proto); - my ($that, $thataddr); + my ($that); my ($savesel, $gotsep); # my ($thishost, $this, $thisaddr); @@ -6330,7 +6480,7 @@ $host = $1; $port = $2; $suf = $3; - if ($port =~ /^\:(\d+)$/) { $port = $1; } + if (defined $port && $port =~ /^\:(\d+)$/) { $port = $1; } else { $port = 80; } ($fqdn, $aliases, $type, $len, $thataddr) = gethostbyname ($host); return &pdv ("Host not found: $host\n") if ($thataddr eq ''); @@ -6345,7 +6495,7 @@ unpack ('C4', $thataddr), $port)); eval { $SIG{'ALRM'} = sub { die "Timeout error on $url\n" }; - alarm (5); + alarm (10); # bind(WWW, $this) || &die_disarm ("bind: $!\n"); # &pdv ("bound the socket...\n"); connect(WWW, $that) || &die_disarm ("connect: $!\n"); @@ -6356,7 +6506,7 @@ ."User-Agent: premail/$version (perl; unix)\n" ."\n"; $response = ; - if ($response !~ /^HTTP\/1\.0 200/) { + if ($response !~ /^HTTP\/1\.\d 200/) { &die_disarm ("Remote server error: $response"); } $gotsep = 0; @@ -6368,6 +6518,21 @@ }; if ($@) { return &pdv ($@); } return &pdv ("No response from server\n") unless $gotsep; + } elsif ($url =~ /^finger:(.*)$/) { + my $target = @RELAYS ? $1 . '@' . $RELAYS[time % @RELAYS] : $1; + &error("'$target' contains no hostname\n") unless ($target =~ /(.*)@([^@]+)/); + my ($user, $host, $port, $ipaddr, $sin) = ($1, $2); + return &pdv ("Unknown host: $host\n") unless ($ipaddr = inet_aton($host)); + &error ("Internal error: unknown service finger\n") + unless $port = getservbyname('finger', 'tcp');; + socket (WWW, PF_INET, SOCK_STREAM, getprotobyname ('tcp')) || + return &pdv ("socket: $!\n"); + $sin = sockaddr_in ($port, $ipaddr); + connect (WWW, $sin) || return &pdv("S! while connecting to $host\n"); + &pdv ("connected to the socket...\n"); + select ((select(WWW), $|=1)[0]); + + print WWW "$user\r\n"; } else { &error ("Misformed URL: $url\n"); } @@ -6380,3 +6545,79 @@ $SIG{'ALRM'} = "IGNORE"; die @_; } + +# Commented out so that we don't depend on GTK, Steve Kostecke 02/27/2000 +# sub create_entry { +# # For some reason, the xterm hack for the passphrase doesn't work +# # on the latest Debian release (2.0Beta). So, I have modified some +# # code from the libgtk-perl package (test.pl program) to pop up a +# # Gtk box to ask for the passphrase. +# # 7/4/98 -- Brent Fulgham +# +# my($box1, $box2, $entry, $button, $separator, $pass_phrase, $label); +# +# init Gtk; +# +# if (not defined $entry_window) { +# $entry_window = new Gtk::Window -toplevel; +# $entry_window->signal_connect("destroy",\&destroy_window,\$entry_window); +# $entry_window->signal_connect("delete_event",\&destroy_window,\$entry_window); +# $entry_window->set_title("Passphrase Entry"); +# $entry_window->border_width(0); +# $box1 = new Gtk::VBox(0,0); +# $entry_window->add($box1); +# show $box1; +# +# $box2 = new Gtk::VBox(0,10); +# $box2->border_width(10); +# $box1->pack_start($box2, 1, 1, 0); +# show $box2; +# +# $entry = new Gtk::Entry; +# $entry->set_usize(0,25); +# $entry->set_visibility(0); +# $entry->select_region(0, length($entry->get_text)); +# $box2->pack_start($entry, 1, 1, 0); +# show $entry; +# +# $separator = new Gtk::HSeparator; +# $box1->pack_start($separator, 0, 1, 0); +# show $separator; +# +# $box2 = new Gtk::VBox(0,10); +# $box2->border_width(10); +# $box1->pack_start($box2,0,1,0); +# show $box2; +# +# $button = new Gtk::Button "Finished"; +# $button->signal_connect("clicked", sub { +# $pass_phrase = $entry->get_text; +# +# destroy_window ($entry_window); +# }); +# $box2->pack_start($button, 1, 1, 0); +# $button->can_default(1); +# $button->grab_default; +# show $button; +# +# $label = new Gtk::Label "Note: No output will appear"; +# $box2->pack_start($label, 1, 1, 0); +# show $label; +# +# } +# if (!visible $entry_window) { +# show $entry_window; +# } +# else { destroy $entry_window }; +# +# main Gtk; +# +# return $pass_phrase; +# } +# +# sub destroy_window { +# my($widget, $windowref, $w2) =@_; +# $$windowref = undef; +# $w2 = undef if defined $w2; +# Gtk->main_quit; +# } --- premail-0.46.orig/preferences +++ premail-0.46/preferences @@ -4,6 +4,8 @@ # Raph Levien # 4 Jul 1996 # +# And as modified to v. 0.46 in Raph's current package +# # To change a setting, remove the # before the $config, and edit the # value after the = sign. For example, to set pgppath to # ~/remailerkeys, change the line @@ -19,8 +21,8 @@ # almost always /usr/lib/sendmail, but if not, set it here. If you're # installing premail as /usr/lib/sendmail, then it's a better idea to # set it within the premail file itself, so that it doesn't depend on -# reading this ~/.premailrc file. Also, if you're installing premail -# as /usr/lib/sendmail, then make sure that none of the ~/.premailrc +# reading this preferences file. Also, if you're installing premail +# as /usr/lib/sendmail, then make sure that none of the preference # files have config{'sendmail'} set to premail -- this will cause a # nasty loop. # @@ -165,7 +167,9 @@ # URL's for information about the remailers. If these URL's are set to # blank, then that disables the process of getting the files from the -# Web. +# Web. The URL's shown below are longer valid and are included for +# historical interest and as an example. Please see +# http://www.noreply.org/allpingers/ for current stats and keyrings. # # $config{'rlist-url'} = 'http://kiwi.cs.berkeley.edu/rlist'; # $config{'pubring-url'} = 'http://kiwi.cs.berkeley.edu/pubring.pgp'; --- premail-0.46.orig/README +++ premail-0.46/README @@ -1,4 +1,5 @@ - p r e m a i l v. 0 . 4 5 + + p r e m a i l v. 0 . 4 5 This is the Halloween Eve release of premail, version 0.45. --- premail-0.46.orig/doc-0.46.txt +++ premail-0.46/doc-0.46.txt @@ -0,0 +1,1053 @@ + + This document is available online at + [1]http://www.c2.net/~raph/premail/. + +Introduction to premail + + This is the documentation for version 0.46 of premail, an e-mail + privacy package by [2]Raph Levien. It is organized as a single, + large document so as to be easily readable when printed. You can, + however, jump directly to one of these topics: [3]installation, + [4]secrets, [5]preferences, [6]Netscape, [7]Pine, [8]other mailers, + [9]command line, [10]encryption, [11]decoding, [12]anonymity, + [13]nyms, [14]usenet, [15]address book, [16]smime, [17]debugging, + [18]technical notes, [19]related documents, (end of list). + + The main function of premail is adding support for encrypted e-mail + to your mailer, using plain PGP, [20]PGP/MIME, [21]MOSS, or + [22]S/MIME. + + In addition, premail provides a seamless, transparent interface to + the [23]anonymous remailers, including full support for Mixmaster + remailers and the nymservers. Nymservers provide cryptographically + protected, fully anonymous accounts for both sending and receiving + e-mail. + + While premail can be used as a stand-alone application, it works + best when integrated with your mailer. Currently, premail is + integrated completely seamlessly and transparently only with + Netscape 3.0's built-in mailer. It works fairly well with [24]Pine + 3.94 or later, as well (plain PGP is supported, but decryption of + MIME-based e-mail encryption protocols is still missing). + Transparent integration of outgoing mail only is supported for any + mailer in which the mail sending program can be configured, + including Berkeley mail, most emacs mailers, and [25]MH. For these + mailers, you can decode messages with a single command. + + To integrate with your mailer, premail places itself between the + mailer and the actual mail transport. For outgoing mail, premail + masquerades as sendmail. You configure your mailer to call premail + instead of sendmail. Then, premail performs the encryption or + signing, and invokes sendmail to actually send the message. + + For mailers that call a command to receive incoming mail (including + Netscape 3.0), the situation is similar. Netscape, for example, can + be configured to call movemail to get incoming mail. To integrate + premail, you'd configure Netscape to call premail instead, which + would in turn call movemail to actually get the mail, then would + decode it. + + You need the following software in order to effectively use + premail: + + * Unix. Unfortunarely, premail does not work on Mac or Windows. + * [26]Perl 5.000 or later. + * [27]PGP (version 2.6.2 recommended). + * [28]RIPEM 3.0b2 or later (optional, for S/MIME support) + + [29]TIS/MOSS 7.1 (optional, for MOSS support) + + [30]Mixmaster (optional, for higher security anonymous mail) + + [31]Lynx (only if you're behind a firewall) + +Installation + + First, you need to get premail. The source code is available from + an [32]export-control Web server. You may also be able to find a + copy on the [33]Hacktic FTP site in the Netherlands. In either + case, you want to get the file premail-0.46.tar.gz. + + After you've gotten the file, unpack it. This command should do it: + + gzip -dc premail-0.46.tar.gz | tar xvf - + + The unpacking process will create a subdirectory called + premail-0.46, containing the following files: + + README A short description of the contents + premail The premail program itself + preferences A skeletal preferences file + + Test to see if you can run premail. These commands should print a + usage summary: + + cd premail-0.46 + ./premail + + If you get an error message reading "command not found," then you + will have to edit the first line of premail to refer to the actual + pathname of the perl5 interpreter. One good way to find out the + pathname is to do "which perl5" or "which perl". + + On the other hand, if you get a string of syntax errors, then the + problem is that you are running perl4, while premail needs perl5. + Try to see if you can find perl5 on your machine. Otherwise, you + may need to install perl5 yourself. + + If you will be using premail from the command line frequently, then + you may want to copy (or symlink) the premail program into a + location in your $PATH. For example, if you have permission to add + files into /usr/local/bin, then you may consider running this + command: + + cp -p premail /usr/local/bin + + At this point, you are ready to test whether premail actually + works. We are assuming that you already have PGP installed and have + generated your own public key. Type this command, substituting in + your own e-mail address: + + ./premail -t + To: your@own.email.addr ((encrypt-pgp)) + Subject: Test + + Does this really work? + . + + If all goes well, you should be back at the command line within a + couple of seconds. If it seems to hang without any disk or net + activity, try typing randomly for a minute, under the assumption + that PGP needs random keystrokes. This shouldn't happen if PGP is + already set up correctly (including having generated your own + public key), but on the chance that it isn't, hanging while waiting + for random keystrokes is one of the more common failure modes. + + This is also the point at which you may get a PGP error. Two common + problems are that premail can't find the PGP program, in which case + you will want to add a line to your preferences file (see + [34]below), or that it can't find the public key corresponding to + your e-mail address. + + If the test was successful you now have a PGP-encrypted message in + your mailbox, then you should now have a PGP-encrypted message in + your mailbox. + +Preferences + + While premail's default configuration is designed to be sufficient + for the the most common cases, you may want to change some of the + configuration options. This is done by adding lines to the + preferences file. + + The default location for the preferences file is + ~/.premail/preferences, where ~ represents your home directory. The + premail distribution comes with a skeleton preferences file, but it + does not automatically copy it into the ~/.premail directory. You + might choose to do that yourself, or you might create one from + scratch. + + The format of the preferences file is a sequence of lines such as + the following: + + $config{'option'} = 'value'; + + All other lines (including those beginning with #) are considered + to be comments and are ignored. Here's a typical preferences file + (actually, the one on my home machine): + + $config{'logfile'} = '/home/raph/premail/log'; + $config{'debug'} = 'chvl'; + $config{'movemail'} = '/home/raph/bin/movehome'; + $config{'ripem'} = '/home/raph/install/ripem/main/ripem'; + $config{'pgp'} = '/usr/local/bin/pgp'; + + As you can see, a major use for the preferences file is to specify + full pathnames for the helper programs. In addition, I've set it up + to produce a full log, which I find useful, because I'm constantly + tracking down bugs :-) + + Here's a table of all the configuration options, their defaults, + and a very brief description. More complete descriptions are found + in the preferences file included in the premail distribution. + + _option + default_ _explanation_ + pgp + _pgp_ The location of the PGP executable. + sendmail + _/usr/lib/sendmail_ The location of the sendmail executable. + mixmaster + _mixmaster_ The location of the Mixmaster executable (useful for more + secure anonymous mail). + movemail + _movemail_ The location of the movemail executable (useful for + integrating Netscape 3.0). + ripem + _ripem_ The location of the ripem executable (needed for S/MIME + messages). + mossbin + __The directory containing the TIS/MOSS executables (needed for MOSS + messages). + post + _post_ The location of the MH post executable (needed for MH + integration). + geturl + __A command for getting files from the Web. Use "lynx -source" if + behind a firewall. + dead-letter + _~/dead.letter_ The file where premail stores undeliverable mail. + logfile + __The location where premail stores its log, if the l debug flag is + set. + storefile + __If set, the location where premail stores outgoing mail, instead of + calling sendmail. + tmpdir + _/tmp_ Where premail stores its temporary files. + charset + _iso-8859-1_ The default charset for outgoing 8-bit messages. + encrypt + _yes_ Set to blank to disable PGP encryption to remailers. + ack + __If set, nymservers will send acknowledgements for all outgoing mail. + extrablank + __If set, premail adds an extra blank on remailer messages. Useful if + behind a broken mail proxy. + debug + __Debugging flags (see section on [35]debugging). + signuser + __The user id of the default PGP secret key used to sign messages. + default-reply-to + __Adds a Reply-To: header field with this address when sending + anonymous e-mail. + addresses + _~/.premail/addresses_ The file containing your addresses. + rlist + _~/.premail/rlist_ The file where premail stores the remailer list. + pubring + _~/.premail/pubring.pgp_ The file where premail stores the public + keyring for the remailers. + premail-secrets-pgp + _~/.premail/secrets.pgp_ The file where premail stores the encrypted + secrets file. + premail-secrets + _/tmp/premail-secrets.$<_ The location of your secrets file + rlist-url + _http://kiwi.cs.berkeley.edu/rlist _The URL for the remailer list. + pubring-url + _http://kiwi.cs.berkeley.edu/pubring.pgp_ The URL for the remailer + public keyring. + type2-list-url + _http://www.jpunix.com/type2.html_ The URL for the Mixmaster type2 + list. + pubring-mix-url + _http://www.jpunix.com/pubring.html_ The URL for the Mixmaster + pubring. + +Secrets + + To create signatures, decrypt messages, or use nyms, you need to + set up a "premail secrets" file. If you will only be using premail + to encrypt outgoing mail, you can skip this section. + + The default filename is /tmp/.premail-secrets.$< , where $< is + equal to your numeric user id. To change the filename, use a + preferences line such as this one: + + $config{'premail-secrets'} = '/mnt/cryptdisk/premail-secrets'; + + If you don't know your numeric user id, you can find it by running + "echo $uid" (from csh or tcsh), "echo $UID" (from sh or bash), or: + + perl -e 'print "$<\n"' + + The premail secrets file has this format: + + $pgppass{'user'} = 'PGP passphrase for user'; + $pgppass{'alternate'} = 'PGP passphrase for alternate'; + $penetpass = 'Passphrase for anon.penet.fi'; + + However, make sure your premail secrets file has restrictive + permissions, so other people on your system can't read your + passphrases! This command is well recommended (substituting your + actual user id, of course): + + chmod 600 /tmp/.premail-secrets.7437 + + Logging in and logging out + + Generally, premail stores its secrets file in the /tmp directory. + In some cases, this is good enough security. In other cases, it + might be better to store the file encrypted most of the time, and + only decrypt it when necessary. To use this capability of premail, + first set a passphrase with: + + premail -setpass + + You will be prompted for a passphrase. You can use the same + passphrase as for your PGP key, or a different one, depending on + how many passphrases you want to remember. This command leaves you + logged in with the new passphrase set. + + To log out: + + premail -logout + + You might consider adding this command to your .logout file, so + that it occurs automatically every time you log out of your + account. + + To log in again: + + premail -login + + If you are running on a system with X, then premail will + automatically pop up a window to log in whenever the secrets are + needed. If you are not running X, and the secrets are needed, you + will get an error. In this case, you can log in manually and try + the command again. + +Netscape + + This section describes how to integrate premail into Netscape 3.0's + built-in mailer. Skip this section if you won't be using Netscape + mail. + + 1. Create symbolic links to premail called "prezilla" and + "premailmove". To do this, make sure you are in the same directory + as premail itself, and type: + + ln -s premail prezilla + ln -s premail premailmove + + 2. Find a working movemail. If you have emacs installed, then you + most likely have one in /usr/lib/emacs/etc/movemail or a similar + location. If you don't already have one, then the source (or + possibly binary) for one is included in the Netscape Navigator + distribution and you can build it (no need if a binary is + included). Then, make sure premail can find it by adding a line + such as this one to your preferences file: + + $config{'movemail'} = '/usr/lib/emacs/etc/movemail'; + + This usage assumes that you get your mail from a mail spool, as + opposed to POP or some such. You may be able to get it to work for + POP as well, but you need to figure out how to invoke movemail to + move the mail from your mailbox to a file (specified as the second + argument to the movemail script). + + 3. Add this line to your .cshrc, assuming your shell is csh or + tcsh: + + setenv NS_MSG_DELIVERY_HOOK /your/path/to/prezilla + + Also run this command from the shell so it takes effect + immediately. The syntax is slightly different if your shell is sh + or bash _(note: is this right?)_: + + NS_MSG_DELIVERY_HOOK=/your/path/to/prezilla + export NS_MSG_DELIVERY_HOOK + + 4. Start Netscape (exit first if it's already running). Go to the + Options|Mail and News Preferences dialog, select the Servers tab. + Click on "External Movemail" and set the value to + /your/path/to/premailmove. + + Try sending yourself mail, and clicking on "Get Mail" from the + Netscape Mail window. The mail should show up in the Inbox, + correctly decoded. + + To view the X-Premail-Auth: header field to see the result of + signature checking, select Options|Show All Headers from the + Netscape Mail window. + + Note: as of Netscape v3.0, there is still a bug in the handling of + the Bcc: header field, which causes it to be ignored. Do not use + this field. Hopefully, this will be fixed in a future version of + Netscape. + + Note: some 3.0 beta versions modify the PATH environment variable. + If premail seems to work correctly from the command line, but not + from Netscape, try setting absolute pathnames for the programs used + by premail. + +Pine + + As of Pine 3.94, premail integrates both outgoing mail and the + decryption of plain PGP incoming mail. Unfortunately, decryption of + MIME-based mail is not yet supported. + + Two Pine configuration options need to be set to integrate premail + (i.e. from the main Pine screen, S for setup, then C for + configure). First, sendmail-path should be set to a value similar + to this (substituting the actual path to premail): + + /your/path/to/premail -oem -t -oi + + Second, display_filters should be set to a value similar to this: + + _BEGINNING("-----BEGIN PGP")_ /your/path/to/premail -decode -body + + If you have trouble finding these options in the setup screen, then + you can edit the .pinerc file directly. + + One caveat when using Pine: it usually tries to be "smart" and + remove comments from e-mail addresses, which includes the + double-paren commands such as ((encrypt-pgp)). There are a few ways + to deal with this problem: + + * Use "( )" instead of (( )). _Note: I think this works, but I + haven't tested it._ + * Use the alternative caret syntax. These two lines mean the same + thing: + + To: raph@cs.berkeley.edu ((encrypt-key, sign)) + To: raph@cs.berkeley.edu^encrypt-key^sign + * Avoid setting the encryption options on the command line + altogether, and set them in the addresses file instead (see + [36]below). + +Other mailers + + This section describes how to integrate premail with MH, emacs, and + UCBMail. With these mailers, premail will only handle outgoing mail + automatically. To decode incoming mail, you still need to invoke + premail -decode by hand. + + Integrating premail with Emacs + + To add premail support to emacs, just add this line to your .emacs + file: + + (setq sendmail-program "/your/path/to/premail") + + Integrating premail with MH + + In whatever directory you keep the premail executable, create a + symbolic link as follows: + + ln -s premail prepost + + Under the name "prepost", premail will masquerade as MH's post + program rather than sendmail. You can get MH to call premail + instead of post by adding this line to your .mh_profile: + + postproc: /your/path/to/prepost + + One thing to keep in mind is that premail's processing is done + before that of post. Thus, if you have MH aliases, they will get + expanded after the call to premail. If you use only premail + aliases, only MH aliases, or neither, this won't be a problem. + + Alternatively, if you have appropriate privileges, you can add this + line to /usr/lib/mh/mtstailor: + + sendmail: /your/path/to/premail + + You may also have to configure MH to call sendmail locally rather + than connecting to an SMTP server. Don't do both the mtstailor and + mh_profile methods -- that would run premail twice. + + Installing premail with UCBmail + + UCBmail is a simple mailer front-end (also known as Mail and + mailx). If, when you type "mail user@site.dom", the mailer asks you + for a "Subject: " line, you are undoubtedly using UCBmail. If so, + you are in luck - it integrates very easily with premail. Just add + this line to your ~/.mailrc file: + + set sendmail=/your/path/to/premail + + Using premail with UCBmail is not very different from using premail + by itself, but you do get some handy features, such as including + files and using an editor on the mail. + +Command line + + Hopefully, you have integrated premail into your mail client, and + you won't have to invoke it from the command line. However, there + may still be times when it is convenient to use premail from the + command line. + + The most basic use of premail is as a replacement for sendmail. For + example, you can send mail directly from the command line, as + follows (here, the > represents the Unix prompt): + + > premail -t + To: raph@cs.berkeley.edu ((sign)) + Subject: premail bug report + + Here's a bug in premail: ... + . + > + + The -t option specifies that the recipients are extracted from the + header fields (To:, Cc:, Bcc:, and the Resent- variants of each). + As in sendmail, you can specify the recipients on the command line + instead of using the -t option. + + In addition, you can set configuration options from the command + line, using the +option=value syntax. This is especially useful + with the [37]debug option. For example, to show you what happens + when formatting mail for remailers, but not actually send the + message: + + + > premail +debug=ry -t + To: raph@cs.berkeley.edu ((chain=1)) + Subject: test of remailer + + test + . + Chose chain exon + /usr/lib/sendmail -oi remailer\@remailer\.nl\.com + + There is one configuration option that can only be set from the +command line in this fashion, which is the location of the preferences +file itself. The configuration option is preferences, and the +default value is ~/.premail/preferences. + + +Encryption + + Once you've got premail set up, actually using encryption is easy. + You simply add commands in double parentheses to the e-mail + addresses. The encrypt-pgp command (which can be abbreviated to + key) adds encryption to the outgoing mail, and the sign command + signs it. + + For example, to send me encrypted mail, you'd send it to + raph@cs.berkeley.edu ((encrypt-pgp)). You need to have a key with + this user id on your PGP public keyring, otherwise you'll get an + error message. If the user id on the key doesn't match the e-mail + address, you can specify it directly. For example, to send mail + directly to my workstation, but using the same public key as above, + use raph@kiwi.cs.berkeley.edu ((key=raph@cs.berkeley.edu)). + + Signing works much the same way. I can sign mail by adding + ((sign=raph@cs.berkeley.edu)) to the outgoing address. Actually, + because I set the signuser configuration option in my preferences + file, all I have to add is ((sign)). + + Doing both encryption and signing is just as easy. For example, to + send me signed, encrypted mail, use this line: + + To: raph@cs.berkeley.edu ((encrypt-pgp, sign)) + + Each recipient is treated separately - the double-paren commands + after an e-mail address apply to that recipient only. However, you + can add a Sign: header field to indicate that your message is + signed for all recipients. Example: + + To: vp@company, secretary@company, employees@company, + friend@outside ((encrypt-pgp)) + Subject: Important announcement + Sign: + + ... + + In this example, all recipients will get a signed message, and the + message to friend@outside will be encrypted as well. + +Decoding + + The basic way to decode encrypted messages is to use premail + -decode as a command line. You can either give a filename as an + argument, or premail will accept the encrypted message on its + standard input. In either case, the decoded message will be printed + on the standard output. + + The message can be a standard e-mail message (RFC 822 format), or + it can be an entire mailbox. In the latter case, premail will + decode each of the messages individually. If you don't have premail + directly integrated into your mailer, then here's a handy way to + view your mail: + + premail -decode $MAIL | more + + If the message is actually encrypted, then premail will need to + access the secrets file. If you are logged out of premail, then + premail will try to open an xterm window for you to type the + passphrase for the secrets file. If that doesn't succeed, premail + will print an error message. At that point, you might choose to log + in (i.e. premail -login) and then try the decoding again. + + If, as in many mailers, you have easy access to the body of the + message but not the header, then you can use premail -decode -body + on the body. This works well for plain PGP encrypted messages, but + unfortunately does not work for MIME-based message formats, because + important information is contained in the header. + + The results of the decoding (including signature verification) are + given in an X-Premail-Auth: header field. This header field is + protected against forgery; if the original message contains it, it + is changed to X-Attempted-Auth-Forgery. + +Anonymity + + The original reason for writing premail was to provide good support + for [38]anonymous remailers. If you're not interested in sending + anonymous mail, you can skip this section. + + Sending anonymous mail is very similar to sending encrypted mail. + Simply add the ((chain)) command to the recipient's e-mail address. + Alternatively, you can add a Chain: header field, and the mail will + be send anonymously to all recipients. + + Even though the chain command is simple, a lot is going on under + the surface. The default chain is 3, which asks that three "good" + remailers be chosen randomly. To make sure that it makes its choice + based on fresh, up-to-date information, premail downloads the + remailer list and a set of PGP public keys for the remailers from + the Web (the actual URLs are configuration options). After choosing + the remailers, the message is multiply encrypted with the PGP + public keys, and finally sent to the first remailer in the chain. + + The automatic chain selection process is very good. My tests + indicate that reliability is consistently above 99%. Further, the + chain selection process avoids some potential problems. For + example, some remailers are known not to work well in chains, + probably because of incorrectly configured "block lists." Also, + some remailers are "linked," in the sense of being hosted on the + same machine, or being administered by the same person. Choosing a + sequence of linked remailers wouldn't offer much security, so + premail doesn't. + + You can also choose the chain length. A shorter chain will be + faster and more reliable, but less secure, and conversely for + longer chains. For example, ((chain=5)) selects a chain of five + remailers. + + If this isn't enough control, you can specify the exact chain of + remailers by hand. For example, ((chain=replay;jam;exon)) bounces + the message around a few times outside the US. + + Mixmaster chains are specified inside an additional set of + parentheses. At the moment, there is no way to automatically select + a chain of Mixmaster remailers, so you have to do it by hand. For + example: ((chain=(replay;ecafe-mix;lcs))). You can even mix + Mixmaster and type-1 remailers; for example, + ((chain=(anon);1;(replay))) will sandwich one well-chosen remailer + between the two Mixmaster remailers. + + Extra header fields can be placed in the outgoing message by + prefixing the header with "Anon-". A particularly common usage is + an Anon-Reply-To: field, which specifies a reply-to address in the + mail delivered to the recipient. The Reply-To: header field is used + often enough that premail includes a default-reply-to configuration + option, which automatically adds it to all anonymous messages. + + The following header fields are passed through to the anonymized + message, even without the Anon- prefix: + + Mime-Version: + Content-Type: + Content-Transfer-Encoding: + Newsgroups: + X-Anon-To: + In-Reply-To: + References: + +Using nyms + + This section describes how to create and use _nyms_, which are + accounts for sending and receiving anonymous mail. There are two + types of nymservers: alpha (named after the now defunct + alpha.c2.org), and newnym. For the most part, the operation of the + two is similar. + + To create a new nym, type + + premail -makenym + + and follow the prompts. This command is also good for updating an + existing nym, which is important if one of the nym's remailers goes + down. + + You can also create or update a nym from the command line, as + follows: + + premail -makenym you@alias.cyberpass.net your@real.email.address + + When premail creates a nym, it chooses random passphrases (one for + each remailer in the chain). The passphrases and other details of + the nym are stored in the premail secrets file. Thus, the nym is + fairly secure (much more so than, say, anon.penet.fi). + + The decode mechanism handles responses to nyms, again looking up + the passphrases in the premail secrets file. + + You can also send mail from your nym, in one of two ways. Assume + for the sake of example that your nym is you@alias.cyberpass.net. + Then, you would use a chain of 2;cyber=you. Alternatively, you can + use a chain of 2;cyber and include this header field: + + Anon-From: you@alias.cyberpass.net (You Know Who) + + If you want the nymserver to send you a confirmation every time you + send mail from your nym, add a $config{'ack'} = 'yes'; line to your + preferences file. + + To delete a nym: + + premail -makenym you@alias.cyberpass delete + + Please delete nyms if you are not actually using them; this helps + free up disk space and prevents the nymservers from being + overloaded. + + As of version 0.46, premail now supports the newnym type of + nymserver. This nymserver is more richly featured than the alpha + type. You do have to answer a few more prompts when creating nyms + for the newnym type, including creating a new PGP key. It's worth + it, though. The newnym servers seem to be working a lot better than + the alpha ones ever did. For more information on newnym, see the + [39]nym.alias.net homepage. If you want to exchange nyms between + premail and other programs (or a manual setup), then take a look at + the -importnym and -exportnym commands, which are explained in the + documentation for the [40]patch that upgraded premail 0.44 to have + newnym capability. + +Posting to Usenet + + Even though some remailers can post directly to Usenet, premail + does not support that. Thus, if you want to post to Usenet, you + should use a mail-to-news gateway. + + To find a working mail-to-news gateway, check Don Kitchen's + [41]list. There are two basic kinds: sites that scan the header + fields, and sites that include the newsgroup in the address. + + Using the address-parsing kind, to post to alt.anonymous, you'd + just send mail to alt.anonymous@myriad.alias.net (assuming, of + course, that myriad.alias.net is still functioning). + + Using the header-scanning kind, send mail to + mail2news@myriad.alias.net, and include this header field: + + Newsgroups: alt.anonymous + + The header scanning kind has one advantage: you can cross-post to + multiple newsgroups using one mail message. + + One frequently asked question is: how can I follow up on a thread + while posting anonymously? This is easy. Find the Message-Id: + header field in the post you're responding to, and change it into a + References: field in your outgoing mail. + + Here's an example that ties it all together. Let's say you wanted + to reply to this post: + + From: Edward Brian Kaufman + Newsgroups: alt.privacy.anon-server, alt.anonymous + Subject: A few questions about anon posts + Message-ID: + + Hi, + + I'd like to know what the best/easiest way to do anon posts is and + how to do them. Thank you, + + Ed + + To post the reply anonymously, send this mail: + + To: mail2news@myriad.alias.net ((chain)) + Cc: Edward Brian Kaufman ((chain)) + Newsgroups: alt.privacy.anon-server, alt.anonymous + Subject: Re: A few questions about anon posts + References: + + If you have a Unix machine, using premail is the best way. To find + out how, read the manual. + +Address book + + Adding the extra encryption commands is not difficult, but it can + be tedious and potentially error prone. Thus, premail provides an + address book for specifying commands to be used with specific + e-mail addresses. + + For example, let's say that one of your correspondents tells you + that she prefers mail to be PGP encrypted. Then, instead of typing + ((encrypt-pgp)) every time you send her mail, you could add this + line to your addresses file: + + her@email.address: ((encrypt-pgp)) + + The addresses file is usually at ~/.premail/addresses, but the + location is a configurable option. + + Another example was the hackerpunks mailing list (now defunct), in + which all of the subscribers have alpha.c2.org nyms. Since + haqr@alpha.c2.org had this line in his addresses file, he was able + to post to the list with just "To: hpunks": + + hpunks: hackerpunks@alpha.c2.org ((chain=2;alpha=haqr)) + + An address book entry can also expand to a list of addresses. For + example: + + alice: alice@crypto.com ((encrypt-pgp)) + bob: bwhite@got.net ((key=bobw@netcom.com)) + eric: eric@ecsl.org ((encrypt-pgp)) + + friends: alice, bob, eric + + Sending mail to friends would then do what you'd expect: send + encrypted mail to each of alice, bob, and eric's full e-mail + addresses. + +S/MIME + + Version 0.46 of premail contains limited support for S/MIME + messages. Basic message formatting works, but there are problems + with creating usable certificates, and there is still no support + for an encryption algorithm interoperable with RC2. However, a few + hearty souls may wish to experiment with the S/MIME functionality + that is present. This section explains how to do it. + + First, you must install RIPEM 3.0b2 (or later). This is available + from the ripem export-controlled [42]FTP site. You'll need to get + an account on the server in order to download any of the + export-controlled code - the [43]GETTING_ACCESS file on the site + explains how. + + Once you have RIPEM installed (and the ripem configuration option + pointing to the executable), create a public key with this command: + + premail -ripemkey + + You will then be prompted for your e-mail address. Alternatively, + you can give your e-mail address as a command line argument to + premail -ripemkey. + + After your key is created, you can send signed messages by adding + the ((ssign)) command. If you send a signed message to another + premail user, they will have your public key, and can send you + mail, by using ((encrypt=your@user.id)). + + The default encryption is Triple-DES. If the recipient can't handle + it, then ((encrypt-des)) will fall back to plain DES, which most + users will be able to decrypt - probably including "export" + versions of S/MIME. Of course, the disadvantage of using plain DES + is that any competent spy organization will also be able to decrypt + the messages ;-). + + Unfortunately, RIPEM 3.0b2 has some significant differences from + other S/MIME implementations in the way it handles public key + certificates. These prevent you from getting a VeriSign certificate + you can use. It is, however, possible to accept VeriSign class 1 + beta certificates by running the following (prompts and messages + are in normal font, what you type is in boldface; you can find out + the password by looking in the secrets file): + + > _rcerts -u your@user.id_ + Enter password to private key: + E - Enable standard issuers... + _...other choices..._ + Enter choice: + _e_ + ...V - VeriSign something or other... + _v_ + Enter the number of months the certificate will be valid, or blank to canc +el: + _12_ + Enter choice: + _q_ + +Debugging + + If you run into trouble with premail, it might be of value to turn + on some of the debugging options. This can be done on the command + line, or in the .premailrc file. In the former case, add a + +debug=chvy argument to the command line. In the latter case, try: + + $config{'debug'} = 'chvy'; + + Here are the meanings of the debug options: + + c: Print command line invocation. + h: Print headers of input message. + l: Debug output goes to log instead of stdout. + p: Print finished message, do PGP. + r: Print chain chosen (useful in debugging chain selection). + y: Print finished message, don't do PGP. + v: Print all kinds of verbose info. + + Note that +debug=p puts the encrypted message on stdout. This may + be useful for constructing reply blocks, among other things. + + If there are problems with premail, then one of the best ways to + track them down is through the log. Try setting the debug + configuration option to chvl, setting the logfile configuration + option (for example, to ~/.premail/log), and then examining the + log. Also, if you're bringing bugs to my attention, it helps a lot + if you can send me relevant excerpts from the log. + +Technical notes + + This section covers a number of techincal notes related to the + operation of premail. This information should not be necessary for + ordinary use. + + Multiple recipients + + One of the tricky problems with mail encryption packages such as + premail is how to deal with multiple recipients. Based on + experience with previous versions, this version of premail tries + very hard to "get it right." However, as a consequence, the exact + behavior can sometimes be difficult to understand. + + The hard part is when some of the recipients have encryption + specified and others don't. What premail does is to split the + recipients up into groups. If two recipients can receive the same + actual message, they are in the same group, otherwise not. For + example, recipients getting an encrypted and an unencrypted message + cannot be in the same group. However, multiple recipients appearing + in To: and Cc: fields that use the same encryption method will be + in the same group. A single message, encrypted to multiple + recipients, will be sent, which is considerably more efficient than + encrypting separately for each recipient. + + One subtle point is the handling of Bcc: recipients. The semantics + of Bcc: specify that the mail be sent to each of the Bcc: + recipients, but that none of the other recipients be able to find + out their identity. However, encrypting to multiple recipients + would defeat this, because it is possible to indentify all of the + recipients of the encrypted message. Thus, each encrypted Bcc: + recipient gets its own group. + + Each recipient of an anonymous message also gets its own group, for + similar reasons. + + An attempt is made to make the headers in the message received by + the recipient be the same as if no encryption were used. + Specifically, the complete To: and Cc: header fields will be + present, but the Bcc: field will be missing. One exception to this + rule is anonymous messages, in which case the recipient can't see + any information about the other recipients. + + Error handling + + The goal is to handle errors in the same way as sendmail. Thus, the + exact handling depends on the setting of the -oe command line + option. The default (as in sendmail) is -oep, meaning that the + error message is printed to standard out, and the mail message is + appended to the dead letter file (the location of which is a + configuration option). + + Another choice is -oem, in which case the error message and the + mail message are packaged together and mailed back to the user. + This is appropriate when the mailer has no way to deal with error + messages returned from premail. + + One additional choice, not provided by sendmail, is -oed, which + prints the error message on standard out, but drops the mail + message. This is a good choice if the mailer can interpret a + non-zero return status code as indication of an error. This is the + mode used by Netscape (and is automatically selected when premail + is invoked as prezilla). + + Security issues + + In designing premail, usefulness and convenience were considered + more important than top security. Nonetheless, it can provide good + security, especially if you are aware of the security issues. + + One overriding assumption was that your machine is secure, and that + the serious threats were those of eavesdroppers on the network and + e-mail forgers. In general, premail handles passive attacks quite + well, while containing a number of vulnerabilities to active + attacks. + + Here are some potential security pitfalls with premail: + + * Stores secrets information on disk file. + * Stores (potentially sensitive) temporary files on disk. + * Does not check authenticity of remailer list, remailer public key + ring, or Mixmaster information gotten from the Web. + * Accessing the Web signals when anonymous mail is about to be sent, + perhaps aiding traffic analysis. + * Does not evaluate the trustworthiness of public keys used for + encryption and signature checking. + + Useless features + + Over the years, premail has accumulated a number of features of + dubious value. One of them is support for MOSS, a nice encryption + protocol that nevertheless failed to catch on. If you feel the urge + to use it, documentation is available in the [44]release notes for + version 0.43. + + One potentially cool feature is a server for decoding e-mail. This + _would_ be a useful feature if there were any mailers which used + it. The protcol for the server was designed to be fast (much, much + faster than invoking premail -decode separately for each message), + as well as "crypto-neutral," meaning that it doesn't contain any + features designed just for crypto, and that it could be used for + other tasks, for example converting image formats or character + sets. Thus, a client designed to use this protocol would like be + fully exportable from the US. If you're interested in integrating + support for this protocol into a popular e-mail client, please get + in touch with me. + +Related documents + + * The [45]README file for premail version 0.33a. + * [46]Release notes for version 0.43 of premail. + + _________ + + [47]premail home + +References + + 1. http://www.c2.net/~raph/premail/ + 2. http://kiwi.cs.berkeley.edu/~raph/ + 3. file://localhost/home/raph/premail/doc-0.46.html#install + 4. file://localhost/home/raph/premail/doc-0.46.html#secrets + 5. file://localhost/home/raph/premail/doc-0.46.html#pref + 6. file://localhost/home/raph/premail/doc-0.46.html#netscape + 7. file://localhost/home/raph/premail/doc-0.46.html#pine + 8. file://localhost/home/raph/premail/doc-0.46.html#other + 9. file://localhost/home/raph/premail/doc-0.46.html#command + 10. file://localhost/home/raph/premail/doc-0.46.html#encrypt + 11. file://localhost/home/raph/premail/doc-0.46.html#decode + 12. file://localhost/home/raph/premail/doc-0.46.html#anon + 13. file://localhost/home/raph/premail/doc-0.46.html#nyms + 14. file://localhost/home/raph/premail/doc-0.46.html#usenet + 15. file://localhost/home/raph/premail/doc-0.46.html#address + 16. file://localhost/home/raph/premail/doc-0.46.html#smime + 17. file://localhost/home/raph/premail/doc-0.46.html#debug + 18. file://localhost/home/raph/premail/doc-0.46.html#notes + 19. file://localhost/home/raph/premail/doc-0.46.html#docs + 20. http://www.c2.net/~raph/pgpmime.html + 21. http://www.tis.com/docs/Research/moss.html + 22. http://www.rsa.com/rsa/S-MIME/ + 23. http://www.cs.berkeley.edu/~raph/remailer-list.html + 24. http://www.cac.washington.edu/pine/ + 25. http://www.smartpages.com/faqs/mh-faq/part1/faq.html + 26. http://www.perl.com/perl/index.html + 27. http://web.mit.edu/network/pgp-form.html + 28. ftp://ripem.msu.edu/pub/crypt/ripem/ + 29. http://www.tis.com/docs/Products/tismoss.html + 30. http://www.obscura.com/~loki/ + 31. http://www.ukans.edu/about_lynx/about_lynx.html + 32. http://kiwi.cs.berkeley.edu/premail-form.html + 33. ftp://ftp.hacktic.nl/pub/replay/pub/remailer/ + 34. file://localhost/home/raph/premail/doc-0.46.html#pref + 35. file://localhost/home/raph/premail/doc-0.46.html#debug + 36. file://localhost/home/raph/premail/doc-0.46.html#address + 37. file://localhost/home/raph/premail/doc-0.46.html#debug + 38. http://www.cs.berkeley.edu/~raph/remailer-list.html + 39. http://kiwi.cs.berkeley.edu/~raph/n.a.n.html + 40. http://kiwi.cs.berkeley.edu/~raph/n.a.n.premail-info + 41. http://students.cs.byu.edu/~don/mail2news.html + 42. ftp://ripem.msu.edu/pub/crypt/ripem/ + 43. ftp://ripem.msu.edu/pub/crypt/ripem/GETTING_ACCESS + 44. http://www.c2.net/~raph/premail/premail.notes.0.43 + 45. file://localhost/home/raph/premail-readme.html + 46. file://localhost/home/raph/premail/premail.notes.0.43 + 47. file://localhost/home/raph/premail.html --- premail-0.46.orig/debian/postinst +++ premail-0.46/debian/postinst @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +# Source debconf library. +. /usr/share/debconf/confmodule + +echo "Please back-up your ~/.premail/secrets and ~/.premail/secrets.pgp files." + +#DEBHELPER# --- premail-0.46.orig/debian/control +++ premail-0.46/debian/control @@ -0,0 +1,17 @@ +Source: premail +Section: contrib/mail +Priority: optional +Maintainer: Steve Kostecke +Build-Depends: debhelper +Standards-Version: 3.6.1.1 + +Package: premail +Architecture: all +Depends: pgp, mail-reader +Description: An e-mail privacy package. + Premail adds support for encrypted e-mail to your mailer, using plain PGP, + PGP/MIME, MOSS, or S/MIME. In addition, premail provides a seamless, + transparent interface to the anonymous remailers, including full support + for Mixmaster remailers and the nymservers. Nymservers provide + cryptographically protected, fully anonymous accounts for both sending and + receiving e-mail. --- premail-0.46.orig/debian/README.debian +++ premail-0.46/debian/README.debian @@ -0,0 +1,17 @@ +premail for DEBIAN +---------------------- + +This version of premail has been modified to work with Perl-5.6. +It is strongly suggested that the user make a back-up copy of their +~/.premail/secrets file before using this version of premail. + +Steve Kostecke Sun, 18 Mar 2001 23:12:19 -0500 + +This is a small bug-fix version, based on new sources sent to me by +Raph Levien, the program's creator. Because of some problems I observed +with the pop-up console used to get the user passphrase (in cases where +the user does not login to premail), I have added a small Gtk pop-up +widget to ask for the password. Any problems with this particular part of +the package are entirely my fault, and inquiries should be directed to me. + +Brent A. Fulgham , Sun, 5 Jul 1998 16:09:33 -0700 --- premail-0.46.orig/debian/rules +++ premail-0.46/debian/rules @@ -0,0 +1,73 @@ +#!/usr/bin/make -f +# MAde with the aid of dh_make, by Craig Small +# Sample debian/rules that uses debhelper. GNU copyright 1997 by Joey Hess. +# Some lines taken from debmake, by Cristoph Lameter. + +# Uncomment this to turn on verbose mode. +# export DH_VERBOSE=1 +config: + dh_testdir + perl -c premail + touch config-stamp + +build: build-stamp +build-stamp: + dh_testdir + test -f config-stamp || make -f debian/rules config + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + rm -f config-stamp + dh_clean + +# Build architecture-independent files here. +binary-indep: + dh_testroot + -rm -rf debian/tmp + test -f build-stamp || make -f debian/rules build + +# Build architecture-dependent files here. +binary-arch: build +# dh_testversion + dh_testdir + dh_testroot + dh_clean + + # Add here commands to install the files into debian/tmp + dh_installdirs + cp `pwd`/premail `pwd`/debian/tmp/usr/bin + # Add prepost support + cd `pwd`/debian/tmp/usr/bin/; ln -s premail prepost + + dh_installmanpages + # Manpage sym-link for prepost + cd `pwd`/debian/tmp/usr/share/man/man1; ln -s premail.1.gz prepost.1.gz + + #dh_installdebconf + dh_installdocs + dh_installexamples preferences + dh_installchangelogs + #dh_installmenu + #dh_installcron + #dh_strip + dh_compress + dh_fixperms + dh_shlibdeps + #dh_makeshlibs + dh_installdeb + dh_gencontrol + dh_builddeb + dh_md5sums + +# Below here is fairly generic really + +binary: clean binary-indep binary-arch + +.PHONY: binary binary-indep binary-arch clean checkroot + +# Local variables: +# mode: makefile +# End: --- premail-0.46.orig/debian/changelog +++ premail-0.46/debian/changelog @@ -0,0 +1,132 @@ +premail (0.46-9) unstable; urgency=low + + * Fixed minor typo in description (closes: #125269). + * Added missing Build-Depends on Debhelper (closes: #261030). + * Added reference to http://www.noreply.org/allpingers/ in the + preferences file (closes: #182607). + * Applied finger patch from Devin Carraway (closes: #134091, #149434). + * Added man-page sym-link for prepost. + * Updated to Standards 3.6.1.1 + + -- Steve Kostecke Sun, 25 Jul 2004 23:28:19 -0400 + +premail (0.46-8) unstable; urgency=low + + * Modified to work with Perl > 5.004; see sub open_pgp() for details. + * Removed Perl-5.004 dependency (closes: #65257, #80727). + * It is suggested that the user make a back up copy of thier + .premail/secrets file before using this version of premail. + + -- Steve Kostecke Sun, 18 Mar 2001 23:12:19 -0500 + +premail (0.46-7) unstable; urgency=high + + * Changed Maintainer: field in the control file. + + -- Steve Kostecke Sat, 3 Jun 2000 00:02:29 -0400 + +premail (0.46-6) unstable; urgency=high + + * New maintainer. + * Changed interpreter from #!/usr/bin/perl to #!/usr/bin/perl5.004 + and changed Perl5 dependency to Perl-5.004 (closes: #55282). + * Changed back to xterm password interface since GTK hack doesn't + work on Potato. + * Install undocumented man page for prepost (closes: #45714). + * Change debian/rules to use dh_installmanpages and + dh_installexamples. + * Commented out subs create_entry and destroy_window. Removed + gtk-perl dependency (closes: #45714). + * Updated to Standards 3.0.0.0 + + -- Steve Kostecke Mon, 7 Feb 2000 22:35:02 -0500 + +premail (0.46-5) unstable; urgency=high + * Correct backup-file problem that prevented Premail being used + as a mail filter. + + -- Brent A. Fulgham Mon, 7 Sep 1999 11:15:10 -0700 + +premail (0.46-4) unstable; urgency=low + * Rebuilt for Perl5 dependency + + -- Brent A. Fulgham Mon, 12 July 1999 11:15:10 -0700 + +premail (0.46-3) unstable; urgency=low + * Updated 'build' stuff for new debian versions + * Closed some bugs -- 34865, 30474, 30476 + * Note: Premail is not maintained upstream. I will continue to fix + bugs as they arise, and am contemplating a fork. Send comments + to my e-mail. + + -- Brent A. Fulgham Fri, 1 June 1999 11:15:10 -0700 + +premail (0.46-2) unstable; urgency=low + * Updated source with new patches, fixed misc. misspellings, etc.: + - Added ability to get finger: URL's for using finger:*@anon.ics.mit.edu + for rlist and pubring + - Added newer sendmail options to valid options list + - Add +language=en to PGP calls -- this needs to be updated for other + languages, too. + - Fix sendmail args building (typo) + - Added koi8-r to valid 8bit charsets list + * updated my e-mail address + + -- Brent A. Fulgham Tue, 4 Aug 1998 21:45:12 -0700 + + +premail (0.46-1) unstable; urgency=low, closes=8872 23710 + + * New maintainer. + * Implemented a Gtk-interface to access password information, since + existing implementation doesn't function properly on Debian systems. + * Added more checks that opened files are actually opened before using them. + * Moved example preference file to /usr/doc/premail/example + + -- Brent A. Fulgham Sun, 5 Jul 1998 16:27:15 -0700 + +premail (0.45-4) stable frozen unstable; urgency=high, closes=8943 15680 10553 13579 22416 + + * New maintainer. I'm the maintain the packages until Brent Fulgham gets + through the new-maintainer procedure. + * Implemented a method to provide secure file creation under /tmp in order + do fix a security problem (closes: Bug#15680) + * Added more my statements to declare local variables as local (closes: + Bug#8943) + * Changed section to contrib/mail (closes: Bug#10553, Bug#22416) + * Corrected name of non-existing manpage (closes: Bug#13579) + + -- Martin Schulze Sat, 30 May 1998 18:33:52 +0200 + +premail (0.45-3) non-free; urgency=low + + * debian/rules: premail(1) linked to /usr/man/man7/undocumented.7.gz. + * install Remi Guyomarch's patch for warning messages. + + -- Karl Sackett Wed, 19 Mar 1997 08:41:11 -0600 + +premail (0.45-2) non-free; urgency=high + + * Man page premail(1) linked to undocumented(7) (bug #6251). + + -- Karl Sackett Tue, 14 Jan 1997 09:06:12 -0600 + +premail (0.45-1) non-free; urgency=high + + * New upstream release. + * Patched to remove secrets file bug. + + -- Karl Sackett Mon, 23 Dec 1996 11:35:41 -0600 + +premail (0.44-1) non-free; urgency=low + + * First Debian release. + * premail: call /usr/bin/perl. + * Added index.html documentation. + + -- Karl Sackett Thu, 19 Sep 1996 10:22:32 -0500 + +Local variables: +mode: debian-changelog +End: + --- premail-0.46.orig/debian/premail.docs +++ premail-0.46/debian/premail.docs @@ -0,0 +1 @@ +README doc-0.46.html doc-0.46.txt --- premail-0.46.orig/debian/prerm +++ premail-0.46/debian/prerm @@ -0,0 +1,4 @@ +#!/bin/sh +set -e + +#DEBHELPER# --- premail-0.46.orig/debian/copyright +++ premail-0.46/debian/copyright @@ -0,0 +1,72 @@ +This is the Debian Linux prepackaged version of premail. + +This package was put together by Brent Fulgham , +from sources obtained from: + + A patch to Raph Levien's latest build v. 0.46 + ftp://ftp.hacktic.nl/pub/replay/pub/remailer/premail-0.45.tar.gz + ftp://ftp.hacktic.nl/pub/replay/pub/remailer/premail/premail.patch + +For more information see: + + http://www.c2.net/~raph/premail.html + http://www.c2.net/~raph/premail/ + +premail is covered under the following copyright: + +# Copyright 1996 Raph Levien +# All rights reserved. +# +# This program is free for commercial and non-commercial use as long as +# the following conditions are adhered to. +# +# Copyright remains Raph Levien's, and as such any Copyright notices in +# the code are not to be removed. If this package is used in a product, +# Raph Levien should be given attribution as the author of the parts of +# the program used. This can be in the form of a textual message at +# program startup or in documentation (online or textual) provided with +# the package. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the copyright notice, +# this list of conditions and the following disclaimer. +# +# 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. +# +# 3. All advertising materials mentioning features or use of this +# software must display the following acknowledgement: This product +# includes software developed by Raph Levien . If more +# than one author is so cited, the list may be combined into one +# sentence. +# +# 4. Use and adaptation of small, specific components of this software +# is actively encouraged, and is exempt from the requirements above. +# +# This software is provided by Raph Levien ``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 the author or contributors 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. +# +# The license and distribution terms for any publically available +# version or derivative of this code cannot be changed. i.e. this code +# cannot simply be copied and put under another distribution license +# [including the GNU Public License.] +# +# The reason behind this being stated in this direct manner is (Eric +# Young's) past experience in code simply being copied and the +# attribution removed from it and then being distributed as part of +# other packages. This implementation was a non-trivial and unpaid +# effort. --- premail-0.46.orig/debian/premail.dirs +++ premail-0.46/debian/premail.dirs @@ -0,0 +1,8 @@ +usr +usr/bin +usr/share +usr/share/man +usr/share/man/man1 +usr/share/doc +usr/share/doc/premail +usr/share/doc/premail/examples --- premail-0.46.orig/debian/template +++ premail-0.46/debian/template @@ -0,0 +1,5 @@ +Template: premail/secrets_backup +Type: note +Description: Please back-up your ~/.premail/secrets(.pgp) files + Please make a back-up copy of your ~/.premail/secrets(.pgp) files before using + this version of premail. --- premail-0.46.orig/debian/config +++ premail-0.46/debian/config @@ -0,0 +1,7 @@ +#!/bin/sh -e + +# Source debconf library. +. /usr/share/debconf/confmodule + +db_input high premail/secrets_backup || true +db_go --- premail-0.46.orig/doc-0.46.html +++ premail-0.46/doc-0.46.html @@ -0,0 +1,1291 @@ +premail documentation + + + +

This document is available online at http://www.c2.net/~raph/premail/. + +

Introduction to premail

+
+ +

This is the documentation for version 0.46 of premail, an e-mail +privacy package by Raph +Levien. It is organized as a single, large document so as to be easily +readable when printed. You can, however, jump directly to one of these +topics: + +installation, + +secrets, + +preferences, + +Netscape, + +Pine, + +other mailers, + +command line, + +encryption, + +decoding, + +anonymity, + +nyms, + +usenet, + +address book, + +smime, + +debugging, + +technical notes, + +related documents, + +(end of list).

+ +

The main function of premail is adding support for encrypted e-mail to +your mailer, using plain PGP, PGP/MIME, MOSS, or S/MIME.

+ +

In addition, premail provides a seamless, transparent interface to +the anonymous +remailers, including full support for Mixmaster remailers and the +nymservers. Nymservers provide cryptographically protected, fully +anonymous accounts for both sending and receiving e-mail.

+ +

While premail can be used as a stand-alone application, it works +best when integrated with your mailer. Currently, premail is +integrated completely seamlessly and transparently only with Netscape +3.0's built-in mailer. It works fairly well with Pine 3.94 or later, as +well (plain PGP is supported, but decryption of MIME-based e-mail +encryption protocols is still missing). Transparent integration of +outgoing mail only is supported for any mailer in which the mail +sending program can be configured, including Berkeley mail, most emacs +mailers, and MH. +For these mailers, you can decode messages with a single command.

+ +

To integrate with your mailer, premail places itself between the +mailer and the actual mail transport. For outgoing mail, premail +masquerades as sendmail. You configure your mailer to call premail +instead of sendmail. Then, premail performs the encryption or signing, +and invokes sendmail to actually send the message.

+ +

For mailers that call a command to receive incoming mail +(including Netscape 3.0), the situation is similar. Netscape, for +example, can be configured to call movemail to get incoming mail. To +integrate premail, you'd configure Netscape to call premail instead, +which would in turn call movemail to actually get the mail, then would +decode it.

+ +

You need the following software in order to effectively use +premail:

+ +
    + +
  • Unix. Unfortunarely, premail does not work on Mac or Windows. + +
  • Perl 5.000 or +later. + +
  • PGP +(version 2.6.2 recommended). + +
  • RIPEM 3.0b2 or +later (optional, for S/MIME support) + +
  • TIS/MOSS 7.1 +(optional, for MOSS support) + +
  • Mixmaster (optional, +for higher security anonymous mail) + +
  • Lynx +(only if you're behind a firewall) + +
+ +
+

Installation

+
+ +

First, you need to get premail. The source code is available from +an export-control +Web server. You may also be able to find a copy on the Hacktic FTP +site in the Netherlands. In either case, you want to get the file +premail-0.46.tar.gz.

+ +

After you've gotten the file, unpack it. This command should do +it:

+ +
+   gzip -dc premail-0.46.tar.gz | tar xvf -
+
+ +

The unpacking process will create a subdirectory called +premail-0.46, containing the following files:

+ +
+ + + + + +
   README A short +description of the contents
   premail The premail +program itself
   preferences A skeletal +preferences file
+ +

Test to see if you can run premail. These commands should print a +usage summary:

+ +
+   cd premail-0.46
+   ./premail
+
+ +

If you get an error message reading "command not found," then you +will have to edit the first line of premail to refer to the +actual pathname of the perl5 interpreter. One good way to find out the +pathname is to do "which perl5" or "which perl". +

+ +

On the other hand, if you get a string of syntax errors, then the +problem is that you are running perl4, while premail needs perl5. Try +to see if you can find perl5 on your machine. Otherwise, you may need +to install perl5 yourself.

+ +

If you will be using premail from the command line frequently, +then you may want to copy (or symlink) the premail program into a +location in your $PATH. For example, if you have permission +to add files into /usr/local/bin, then you may consider +running this command:

+ +
+   cp -p premail /usr/local/bin
+
+ +

At this point, you are ready to test whether premail actually +works. We are assuming that you already have PGP installed and have +generated your own public key. Type this command, substituting in your +own e-mail address:

+ +
+   ./premail -t
+   To: your@own.email.addr ((encrypt-pgp))
+   Subject: Test
+
+   Does this really work?
+   .
+
+ +

If all goes well, you should be back at the command line within a +couple of seconds. If it seems to hang without any disk or net +activity, try typing randomly for a minute, under the assumption that +PGP needs random keystrokes. This shouldn't happen if PGP is already +set up correctly (including having generated your own public key), but +on the chance that it isn't, hanging while waiting for random +keystrokes is one of the more common failure modes.

+ +

This is also the point at which you may get a PGP error. Two +common problems are that premail can't find the PGP program, in which +case you will want to add a line to your preferences file (see below), or that it can't find the public key +corresponding to your e-mail address.

+ +

If the test was successful you now have a PGP-encrypted message in +your mailbox, then you should now have a PGP-encrypted message in your +mailbox.

+ +
+

Preferences

+
+ +

While premail's default configuration is designed to be sufficient +for the the most common cases, you may want to change some of the +configuration options. This is done by adding lines to the +preferences file.

+ +

The default location for the preferences file is +~/.premail/preferences, where ~ represents your home +directory. The premail distribution comes with a skeleton preferences +file, but it does not automatically copy it into the +~/.premail directory. You might choose to do that yourself, +or you might create one from scratch.

+ +

The format of the preferences file is a sequence of lines such as +the following:

+ +
+   $config{'option'} = 'value';
+
+ +

All other lines (including those beginning with #) are +considered to be comments and are ignored. Here's a typical +preferences file (actually, the one on my home machine):

+ +
+   $config{'logfile'} = '/home/raph/premail/log';
+   $config{'debug'} = 'chvl';
+   $config{'movemail'} = '/home/raph/bin/movehome';
+   $config{'ripem'} = '/home/raph/install/ripem/main/ripem';
+   $config{'pgp'} = '/usr/local/bin/pgp';
+
+ +

As you can see, a major use for the preferences file is to specify +full pathnames for the helper programs. In addition, I've set it up to +produce a full log, which I find useful, because I'm constantly +tracking down bugs :-)

+ +

Here's a table of all the configuration options, their defaults, +and a very brief description. More complete descriptions are found in +the preferences file included in the premail distribution.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
option
default
explanation
pgp
pgp
The location +of the PGP executable.
sendmail
+/usr/lib/sendmail
The location of the +sendmail executable.
mixmaster
mixmaster
The +location of the Mixmaster executable (useful for more +secure anonymous mail).
movemail
movemail
The +location of the movemail executable (useful for integrating +Netscape 3.0).
ripem
ripem
The location +of the ripem executable (needed for S/MIME messages).
mossbin
The directory +containing the TIS/MOSS executables (needed for MOSS messages).
post
post
The location +of the MH post executable (needed for MH +integration).
geturl
A command for +getting files from the Web. Use "lynx -source" if behind a +firewall.
dead-letter
~/dead.letter
The file where premail stores undeliverable +mail.
logfile
The location +where premail stores its log, if the l debug flag is +set.
storefile
If set, the +location where premail stores outgoing mail, instead of calling +sendmail.
tmpdir
/tmp
Where +premail stores its temporary files.
charset
iso-8859-1
The +default charset for outgoing 8-bit messages.
encrypt
yes
Set to +blank to disable PGP encryption to remailers.
ack
If set, nymservers will +send acknowledgements for all outgoing mail.
extrablank
If set, +premail adds an extra blank on remailer messages. Useful if behind a +broken mail proxy.
debug
Debugging flags +(see section on debugging).
signuser
The user id of the +default PGP secret key used to sign messages.
default-reply-to
Adds a +Reply-To: header field with this address when sending +anonymous e-mail.
addresses
+~/.premail/addresses
The file containing your +addresses.
rlist
+~/.premail/rlist
The file where premail +stores the remailer list.
pubring
+~/.premail/pubring.pgp
The file where premail +stores the public keyring for the remailers.
premail-secrets-pgp
+~/.premail/secrets.pgp
+ The file where premail stores the encrypted +secrets file.
premail-secrets
+/tmp/premail-secrets.$<
The location of your +secrets file
+ + + + + + + + + +
rlist-url
+http://kiwi.cs.berkeley.edu/rlist
The URL for +the remailer list.
pubring-url
+http://kiwi.cs.berkeley.edu/pubring.pgp
The URL +for the remailer public keyring.
type2-list-url
+http://www.jpunix.com/type2.html
+ The URL for the Mixmaster type2 +list.
pubring-mix-url
+http://www.jpunix.com/pubring.html
The URL for +the Mixmaster pubring.
+ +

Secrets

+
+ +

To create signatures, decrypt messages, or use nyms, you need to +set up a "premail secrets" file. If you will only be using premail to +encrypt outgoing mail, you can skip this section.

+ +

The default filename is /tmp/.premail-secrets.$< , +where $< is equal to your numeric user id. To change the +filename, use a preferences line such as this one:

+ +
+   $config{'premail-secrets'} = '/mnt/cryptdisk/premail-secrets';
+
+ +

If you don't know your numeric user id, you can find it by running +"echo $uid" (from csh or tcsh), "echo $UID" (from sh +or bash), or:

+ +
+   perl -e 'print "$<\n"'
+
+ +

The premail secrets file has this format:

+ +
+   $pgppass{'user'} = 'PGP passphrase for user';
+   $pgppass{'alternate'} = 'PGP passphrase for alternate';
+   $penetpass = 'Passphrase for anon.penet.fi';
+
+ +

However, make sure your premail secrets file has restrictive +permissions, so other people on your system can't read your +passphrases! This command is well recommended (substituting your +actual user id, of course):

+ +
+   chmod 600 /tmp/.premail-secrets.7437
+
+ + +

Logging in and logging out

+ +

Generally, premail stores its secrets file in the /tmp +directory. In some cases, this is good enough security. In other +cases, it might be better to store the file encrypted most of the +time, and only decrypt it when necessary. To use this capability of +premail, first set a passphrase with:

+ +
+   premail -setpass
+
+ +

You will be prompted for a passphrase. You can use the same +passphrase as for your PGP key, or a different one, depending on how +many passphrases you want to remember. This command leaves you logged +in with the new passphrase set. + +

To log out:

+ +
+   premail -logout
+
+ +

You might consider adding this command to your .logout file, so +that it occurs automatically every time you log out of your account. +

+ +

To log in again:

+ +
+   premail -login
+
+ +

If you are running on a system with X, then premail will +automatically pop up a window to log in whenever the secrets are +needed. If you are not running X, and the secrets are needed, you will +get an error. In this case, you can log in manually and try the +command again.

+ +
+

Netscape

+
+ +

This section describes how to integrate premail into Netscape +3.0's built-in mailer. Skip this section if you won't be using +Netscape mail.

+ +

1. Create symbolic links to premail called "prezilla" and +"premailmove". To do this, make sure you are in the same directory as +premail itself, and type:

+ +
+   ln -s premail prezilla
+   ln -s premail premailmove
+
+ +

2. Find a working movemail. If you have emacs installed, then you +most likely have one in /usr/lib/emacs/etc/movemail or a similar +location. If you don't already have one, then the source (or possibly +binary) for one is included in the Netscape Navigator distribution and +you can build it (no need if a binary is included). Then, make sure +premail can find it by adding a line such as this one to your +preferences file:

+ +
+   $config{'movemail'} = '/usr/lib/emacs/etc/movemail';
+
+ +

This usage assumes that you get your mail from a mail spool, as +opposed to POP or some such. You may be able to get it to work for POP +as well, but you need to figure out how to invoke movemail to move the +mail from your mailbox to a file (specified as the second argument to +the movemail script).

+ +

3. Add this line to your .cshrc, assuming your shell is csh or +tcsh:

+ +
+   setenv NS_MSG_DELIVERY_HOOK /your/path/to/prezilla
+
+ +

Also run this command from the shell so it takes effect +immediately. The syntax is slightly different if your shell is sh or +bash (note: is this right?):

+ +
+   NS_MSG_DELIVERY_HOOK=/your/path/to/prezilla
+   export NS_MSG_DELIVERY_HOOK
+
+ +

4. Start Netscape (exit first if it's already running). Go to the +Options|Mail and News Preferences dialog, select the Servers tab. +Click on "External Movemail" and set the value to +/your/path/to/premailmove.

+ +

Try sending yourself mail, and clicking on "Get Mail" from the +Netscape Mail window. The mail should show up in the Inbox, correctly +decoded.

+ +

To view the X-Premail-Auth: header field to see the result of +signature checking, select Options|Show All Headers from the Netscape +Mail window.

+ +

Note: as of Netscape v3.0, there is still a bug in the handling +of the Bcc: header field, which causes it to be ignored. Do +not use this field. Hopefully, this will be fixed in a future version +of Netscape.

+ +

Note: some 3.0 beta versions modify the PATH environment +variable. If premail seems to work correctly from the command line, +but not from Netscape, try setting absolute pathnames for the programs +used by premail.

+ +

Pine

+ +

As of Pine 3.94, premail integrates both outgoing mail and the +decryption of plain PGP incoming mail. Unfortunately, decryption of +MIME-based mail is not yet supported.

+ +

Two Pine configuration options need to be set to integrate premail +(i.e. from the main Pine screen, S for setup, then C +for configure). First, sendmail-path should be set to a value +similar to this (substituting the actual path to premail):

+ +
+   /your/path/to/premail -oem -t -oi
+
+ +

Second, display_filters should be set to a value similar +to this:

+ +
+   _BEGINNING("-----BEGIN PGP")_ /your/path/to/premail -decode -body
+
+ +

If you have trouble finding these options in the setup screen, +then you can edit the .pinerc file directly.

+ +

One caveat when using Pine: it usually tries to be "smart" and +remove comments from e-mail addresses, which includes the double-paren +commands such as ((encrypt-pgp)). There are a few ways to +deal with this problem:

+ +
    + +
  • Use "( )" instead of (( )). +Note: I think this works, but I haven't tested it. + +
  • Use the alternative caret syntax. These two lines mean the same +thing: + +
    +   To: raph@cs.berkeley.edu ((encrypt-key, sign))
    +   To: raph@cs.berkeley.edu^encrypt-key^sign
    +
    + +
  • Avoid setting the encryption options on the command line +altogether, and set them in the addresses file instead (see below). + +
+ +
+

Other mailers

+
+ +

This section describes how to integrate premail with MH, emacs, +and UCBMail. With these mailers, premail will only handle outgoing +mail automatically. To decode incoming mail, you still need to invoke +premail -decode by hand. + +

Integrating premail with Emacs

+ +

To add premail support to emacs, just add this line to your .emacs +file:

+ +
+   (setq sendmail-program "/your/path/to/premail")
+
+ + +

Integrating premail with MH

+ +

In whatever directory you keep the premail executable, create a +symbolic link as follows:

+ +
+   ln -s premail prepost
+
+ +

Under the name "prepost", premail will masquerade as MH's post +program rather than sendmail. You can get MH to call premail instead +of post by adding this line to your .mh_profile:

+ +
+   postproc: /your/path/to/prepost
+
+ +

One thing to keep in mind is that premail's processing is done +before that of post. Thus, if you have MH aliases, they will get +expanded after the call to premail. If you use only premail aliases, +only MH aliases, or neither, this won't be a problem.

+ +

Alternatively, if you have appropriate privileges, you can add this +line to /usr/lib/mh/mtstailor:

+ +
+   sendmail: /your/path/to/premail
+
+ +

You may also have to configure MH to call sendmail locally rather +than connecting to an SMTP server. Don't do both the mtstailor and +mh_profile methods -- that would run premail twice.

+ + +

Installing premail with UCBmail

+ +

UCBmail is a simple mailer front-end (also known as Mail and +mailx). If, when you type "mail user@site.dom", the mailer asks you +for a "Subject: " line, you are undoubtedly using UCBmail. If so, you +are in luck - it integrates very easily with premail. Just add this +line to your ~/.mailrc file: + +

+   set sendmail=/your/path/to/premail
+
+ +

Using premail with UCBmail is not very different from using +premail by itself, but you do get some handy features, such as +including files and using an editor on the mail.

+ +
+

Command line

+
+ +

Hopefully, you have integrated premail into your mail client, and +you won't have to invoke it from the command line. However, there may +still be times when it is convenient to use premail from the command +line.

+ +

The most basic use of premail is as a replacement for sendmail. +For example, you can send mail directly from the command line, as +follows (here, the > represents the Unix prompt):

+ +
+   > premail -t
+   To: raph@cs.berkeley.edu ((sign))
+   Subject: premail bug report
+
+   Here's a bug in premail: ...
+   .
+   >
+
+ +

The -t option specifies that the recipients are extracted +from the header fields (To:, Cc:, Bcc:, and +the Resent- variants of each). As in sendmail, you can +specify the recipients on the command line instead of using the +-t option.

+ +

In addition, you can set configuration options from the command +line, using the +option=value syntax. This is especially +useful with the debug option. For example, to +show you what happens when formatting mail for remailers, but not +actually send the message:

+ +
+
+   > premail +debug=ry -t
+   To: raph@cs.berkeley.edu ((chain=1))
+   Subject: test of remailer
+
+   test
+   .
+   Chose chain exon
+   /usr/lib/sendmail -oi remailer\@remailer\.nl\.com << -eof-
+   To: remailer@remailer.nl.com
+
+   ::
+   Encrypted: PGP
+
+   -----BEGIN PGP MESSAGE----- remailer@remailer.nl.com
+   ::
+   Request-Remailing-To: raph@cs.berkeley.edu
+
+   ##
+   Subject: test of remailer
+
+   test
+   -----END PGP MESSAGE-----
+   -eof-
+
+ +

There is one configuration option that can only be set from the +command line in this fashion, which is the location of the preferences +file itself. The configuration option is preferences, and the +default value is ~/.premail/preferences.

+ +
+

Encryption

+
+ +

Once you've got premail set up, actually using encryption is easy. +You simply add commands in double parentheses to the e-mail addresses. +The encrypt-pgp command (which can be abbreviated to +key) adds encryption to the outgoing mail, and the +sign command signs it.

+ +

For example, to send me encrypted mail, you'd send it to +raph@cs.berkeley.edu ((encrypt-pgp)). You need to have a key +with this user id on your PGP public keyring, otherwise you'll get an +error message. If the user id on the key doesn't match the e-mail +address, you can specify it directly. For example, to send mail +directly to my workstation, but using the same public key as above, +use raph@kiwi.cs.berkeley.edu ((key=raph@cs.berkeley.edu)). +

+ +

Signing works much the same way. I can sign mail by adding +((sign=raph@cs.berkeley.edu)) to the outgoing address. +Actually, because I set the signuser configuration option in +my preferences file, all I have to add is ((sign)).

+ +

Doing both encryption and signing is just as easy. For example, +to send me signed, encrypted mail, use this line:

+ +
+   To: raph@cs.berkeley.edu ((encrypt-pgp, sign))
+
+ +

Each recipient is treated separately - the double-paren commands +after an e-mail address apply to that recipient only. However, you can +add a Sign: header field to indicate that your message is +signed for all recipients. Example:

+ +
+   To: vp@company, secretary@company, employees@company,
+       friend@outside ((encrypt-pgp))
+   Subject: Important announcement
+   Sign:
+
+   ...
+
+ +

In this example, all recipients will get a signed message, and the +message to friend@outside will be encrypted as well.

+ +
+

Decoding

+
+ +

The basic way to decode encrypted messages is to use premail +-decode as a command line. You can either give a filename as an +argument, or premail will accept the encrypted message on its standard +input. In either case, the decoded message will be printed on the +standard output.

+ +

The message can be a standard e-mail message (RFC 822 format), or +it can be an entire mailbox. In the latter case, premail will decode +each of the messages individually. If you don't have premail directly +integrated into your mailer, then here's a handy way to view your +mail:

+ +
+   premail -decode $MAIL | more
+
+ +

If the message is actually encrypted, then premail will need to +access the secrets file. If you are logged out of premail, then +premail will try to open an xterm window for you to type the +passphrase for the secrets file. If that doesn't succeed, premail will +print an error message. At that point, you might choose to log in +(i.e. premail -login) and then try the decoding again.

+ +

If, as in many mailers, you have easy access to the body of the +message but not the header, then you can use premail -decode +-body on the body. This works well for plain PGP encrypted +messages, but unfortunately does not work for MIME-based message +formats, because important information is contained in the header. +

+ +

The results of the decoding (including signature verification) are +given in an X-Premail-Auth: header field. This header field +is protected against forgery; if the original message contains it, it +is changed to X-Attempted-Auth-Forgery.

+ +
+

Anonymity

+
+ +

The original reason for writing premail was to provide good +support for anonymous +remailers. If you're not interested in sending anonymous mail, you +can skip this section.

+ +

Sending anonymous mail is very similar to sending encrypted mail. +Simply add the ((chain)) command to the recipient's e-mail +address. Alternatively, you can add a Chain: header field, +and the mail will be send anonymously to all recipients.

+ +

Even though the chain command is simple, a lot is going on under +the surface. The default chain is 3, which asks that three +"good" remailers be chosen randomly. To make sure that it makes its +choice based on fresh, up-to-date information, premail downloads the +remailer list and a set of PGP public keys for the remailers from the +Web (the actual URLs are configuration options). After choosing the +remailers, the message is multiply encrypted with the PGP public keys, +and finally sent to the first remailer in the chain.

+ +

The automatic chain selection process is very good. My tests +indicate that reliability is consistently above 99%. Further, the +chain selection process avoids some potential problems. For example, +some remailers are known not to work well in chains, probably because +of incorrectly configured "block lists." Also, some remailers are +"linked," in the sense of being hosted on the same machine, or being +administered by the same person. Choosing a sequence of linked +remailers wouldn't offer much security, so premail doesn't.

+ +

You can also choose the chain length. A shorter chain will be +faster and more reliable, but less secure, and conversely for longer +chains. For example, ((chain=5)) selects a chain of five +remailers.

+ +

If this isn't enough control, you can specify the exact chain of +remailers by hand. For example, ((chain=replay;jam;exon)) +bounces the message around a few times outside the US.

+ +

Mixmaster chains are specified inside an additional set of +parentheses. At the moment, there is no way to automatically select a +chain of Mixmaster remailers, so you have to do it by hand. For +example: ((chain=(replay;ecafe-mix;lcs))). You can even mix +Mixmaster and type-1 remailers; for example, +((chain=(anon);1;(replay))) will sandwich one well-chosen +remailer between the two Mixmaster remailers.

+ +

Extra header fields can be placed in the outgoing message by +prefixing the header with "Anon-". A particularly common +usage is an Anon-Reply-To: field, which specifies a reply-to +address in the mail delivered to the recipient. The Reply-To: +header field is used often enough that premail includes a +default-reply-to configuration option, which automatically +adds it to all anonymous messages.

+ +

The following header fields are passed through to the anonymized +message, even without the Anon- prefix:

+ +
+   Mime-Version:
+   Content-Type:
+   Content-Transfer-Encoding:
+   Newsgroups:
+   X-Anon-To:
+   In-Reply-To:
+   References:
+
+ +
+

Using nyms

+
+ +

This section describes how to create and use nyms, which +are accounts for sending and receiving anonymous mail. There are two +types of nymservers: alpha (named after the now defunct alpha.c2.org), +and newnym. For the most part, the operation of the two is similar. +

+ +

To create a new nym, type

+ +
+   premail -makenym
+
+ +

and follow the prompts. This command is also good for updating an +existing nym, which is important if one of the nym's remailers goes +down.

+ +

You can also create or update a nym from the command line, as +follows:

+ +
+   premail -makenym you@alias.cyberpass.net your@real.email.address
+
+ +

When premail creates a nym, it chooses random passphrases (one for +each remailer in the chain). The passphrases and other details of the +nym are stored in the premail secrets file. Thus, the nym is fairly +secure (much more so than, say, anon.penet.fi).

+ +

The decode mechanism handles responses to nyms, again looking up +the passphrases in the premail secrets file.

+ +

You can also send mail from your nym, in one of two ways. Assume +for the sake of example that your nym is you@alias.cyberpass.net. Then, you +would use a chain of 2;cyber=you. Alternatively, you can use +a chain of 2;cyber and include this header field:

+ +
+   Anon-From: you@alias.cyberpass.net (You Know Who)
+
+ +

If you want the nymserver to send you a confirmation every time +you send mail from your nym, add a $config{'ack'} = 'yes'; +line to your preferences file.

+ +

To delete a nym:

+ +
+   premail -makenym you@alias.cyberpass delete
+
+ +

Please delete nyms if you are not actually using them; this helps +free up disk space and prevents the nymservers from being overloaded.

+ +

As of version 0.46, premail now supports the newnym type of +nymserver. This nymserver is more richly featured than the alpha type. +You do have to answer a few more prompts when creating nyms for the +newnym type, including creating a new PGP key. It's worth it, though. +The newnym servers seem to be working a lot better than the alpha ones +ever did. For more information on newnym, see the nym.alias.net +homepage. If you want to exchange nyms between premail and other +programs (or a manual setup), then take a look at the -importnym and +-exportnym commands, which are explained in the documentation for the +patch +that upgraded premail 0.44 to have newnym capability.

+ +
+

Posting to Usenet

+
+ +

Even though some remailers can post directly to Usenet, premail does +not support that. Thus, if you want to post to Usenet, you should use +a mail-to-news gateway.

+ +

To find a working mail-to-news gateway, check Don Kitchen's list. There +are two basic kinds: sites that scan the header fields, and sites that +include the newsgroup in the address.

+ +

Using the address-parsing kind, to post to alt.anonymous, you'd +just send mail to alt.anonymous@myriad.alias.net (assuming, of +course, that myriad.alias.net is still functioning).

+ +

Using the header-scanning kind, send mail to +mail2news@myriad.alias.net, and include this header field:

+ +
+   Newsgroups: alt.anonymous
+
+ +

The header scanning kind has one advantage: you can cross-post to +multiple newsgroups using one mail message.

+ +

One frequently asked question is: how can I follow up on a thread +while posting anonymously? This is easy. Find the Message-Id: +header field in the post you're responding to, and change it into a +References: field in your outgoing mail.

+ +

Here's an example that ties it all together. Let's say you wanted +to reply to this post:

+ +
+   From: Edward Brian Kaufman <ebk8@columbia.edu>
+   Newsgroups: alt.privacy.anon-server, alt.anonymous
+   Subject: A few questions about anon posts
+   Message-ID: <Pine.SUN.3.94L.960630113156@aloha.cc.columbia.edu>
+
+   Hi,
+
+   I'd like to know what the best/easiest way to do anon posts is and
+   how to do them.  Thank you,
+
+   Ed
+
+ +

To post the reply anonymously, send this mail:

+ +
+   To: mail2news@myriad.alias.net ((chain))
+   Cc: Edward Brian Kaufman <ebk8@columbia.edu> ((chain))
+   Newsgroups: alt.privacy.anon-server, alt.anonymous
+   Subject: Re: A few questions about anon posts
+   References: <Pine.SUN.3.94L.960630113156@aloha.cc.columbia.edu>
+
+   If you have a Unix machine, using premail is the best way. To find
+   out how, read the manual.
+
+ +
+

Address book

+
+ +

Adding the extra encryption commands is not difficult, but it can +be tedious and potentially error prone. Thus, premail provides an address +book for specifying commands to be used with specific e-mail addresses. + +

For example, let's say that one of your correspondents tells you +that she prefers mail to be PGP encrypted. Then, instead of typing +((encrypt-pgp)) every time you send her mail, you could add +this line to your addresses file:

+ +
+   her@email.address: ((encrypt-pgp))
+
+ +

The addresses file is usually at ~/.premail/addresses, +but the location is a configurable option.

+ +

Another example was the hackerpunks mailing list (now defunct), in +which all of the subscribers have alpha.c2.org nyms. Since +haqr@alpha.c2.org had this line in his addresses file, he was able to +post to the list with just "To: hpunks":

+ +
+   hpunks: hackerpunks@alpha.c2.org ((chain=2;alpha=haqr))
+
+ +

An address book entry can also expand to a list of addresses. For +example:

+ +
+   alice: alice@crypto.com ((encrypt-pgp))
+   bob: bwhite@got.net ((key=bobw@netcom.com))
+   eric: eric@ecsl.org ((encrypt-pgp))
+
+   friends: alice, bob, eric
+
+ +

Sending mail to friends would then do what you'd expect: +send encrypted mail to each of alice, bob, and eric's full e-mail +addresses.

+ +
+

S/MIME

+
+ +

Version 0.46 of premail contains limited support for S/MIME +messages. Basic message formatting works, but there are problems with +creating usable certificates, and there is still no support for an +encryption algorithm interoperable with RC2. However, a few hearty +souls may wish to experiment with the S/MIME functionality that is +present. This section explains how to do it.

+ +

First, you must install RIPEM 3.0b2 (or later). This is available +from the ripem export-controlled FTP site. You'll need +to get an account on the server in order to download any of the +export-controlled code - the GETTING_ACCESS +file on the site explains how.

+ +

Once you have RIPEM installed (and the ripem +configuration option pointing to the executable), create a public key +with this command:

+ +
+   premail -ripemkey
+
+ +

You will then be prompted for your e-mail address. Alternatively, +you can give your e-mail address as a command line argument to +premail -ripemkey.

+ +

After your key is created, you can send signed messages by adding +the ((ssign)) command. If you send a signed message to +another premail user, they will have your public key, and can send you +mail, by using ((encrypt=your@user.id)).

+ +

The default encryption is Triple-DES. If the recipient can't +handle it, then ((encrypt-des)) will fall back to plain DES, +which most users will be able to decrypt - probably including "export" +versions of S/MIME. Of course, the disadvantage of using plain DES is +that any competent spy organization will also be able to decrypt the +messages ;-).

+ +

Unfortunately, RIPEM 3.0b2 has some significant differences from +other S/MIME implementations in the way it handles public key +certificates. These prevent you from getting a VeriSign certificate +you can use. It is, however, possible to accept VeriSign class 1 beta +certificates by running the following (prompts and messages are in +normal font, what you type is in boldface; you can find out the +password by looking in the secrets file):

+ +
+   > rcerts -u your@user.id
+   Enter password to private key:
+   E - Enable standard issuers...
+   ...other choices...
+     Enter choice:
+   e
+   ...V - VeriSign something or other...
+   v
+     Enter the number of months the certificate will be valid, or blank to cancel:
+   12
+     Enter choice:
+   q
+
+ +
+

Debugging

+
+ +

If you run into trouble with premail, it might be of value to turn +on some of the debugging options. This can be done on the command +line, or in the .premailrc file. In the former case, add a ++debug=chvy argument to the command line. In the latter case, +try:

+ +
+   $config{'debug'} = 'chvy';
+
+ +

Here are the meanings of the debug options:

+ +c: Print command line invocation.
+ +h: Print headers of input message.
+ +l: Debug output goes to log instead of stdout.
+ +p: Print finished message, do PGP.
+ +r: Print chain chosen (useful in debugging chain +selection).
+ +y: Print finished message, don't do PGP.
+ +v: Print all kinds of verbose info.
+ +

Note that +debug=p puts the encrypted message on stdout. +This may be useful for constructing reply blocks, among other things. +

+ +

If there are problems with premail, then one of the best ways to +track them down is through the log. Try setting the debug +configuration option to chvl, setting the logfile +configuration option (for example, to ~/.premail/log), and +then examining the log. Also, if you're bringing bugs to my attention, +it helps a lot if you can send me relevant excerpts from the log.

+ +
+

Technical notes

+
+ +

This section covers a number of techincal notes related to the +operation of premail. This information should not be necessary for +ordinary use.

+ +

Multiple recipients

+ +

One of the tricky problems with mail encryption packages such as +premail is how to deal with multiple recipients. Based on experience +with previous versions, this version of premail tries very hard to +"get it right." However, as a consequence, the exact behavior can +sometimes be difficult to understand.

+ +

The hard part is when some of the recipients have encryption +specified and others don't. What premail does is to split the +recipients up into groups. If two recipients can receive the same +actual message, they are in the same group, otherwise not. For +example, recipients getting an encrypted and an unencrypted message +cannot be in the same group. However, multiple recipients appearing in +To: and Cc: fields that use the same encryption +method will be in the same group. A single message, encrypted to +multiple recipients, will be sent, which is considerably more +efficient than encrypting separately for each recipient.

+ +

One subtle point is the handling of Bcc: recipients. The +semantics of Bcc: specify that the mail be sent to each of +the Bcc: recipients, but that none of the other recipients be +able to find out their identity. However, encrypting to multiple +recipients would defeat this, because it is possible to indentify all +of the recipients of the encrypted message. Thus, each encrypted +Bcc: recipient gets its own group.

+ +

Each recipient of an anonymous message also gets its own group, +for similar reasons.

+ +

An attempt is made to make the headers in the message +received by the recipient be the same as if no encryption were used. +Specifically, the complete To: and Cc: header fields +will be present, but the Bcc: field will be missing. One +exception to this rule is anonymous messages, in which case the +recipient can't see any information about the other recipients.

+ +

Error handling

+ +

The goal is to handle errors in the same way as sendmail. Thus, +the exact handling depends on the setting of the -oe command +line option. The default (as in sendmail) is -oep, meaning +that the error message is printed to standard out, and the mail message is +appended to the dead letter file (the location of which is a +configuration option).

+ +

Another choice is -oem, in which case the error message +and the mail message are packaged together and mailed back to the +user. This is appropriate when the mailer has no way to deal with +error messages returned from premail.

+ +

One additional choice, not provided by sendmail, is -oed, +which prints the error message on standard out, but drops the mail +message. This is a good choice if the mailer can interpret a non-zero +return status code as indication of an error. This is the mode used by +Netscape (and is automatically selected when premail is invoked as +prezilla).

+ +

Security issues

+ +

In designing premail, usefulness and convenience were considered +more important than top security. Nonetheless, it can provide good +security, especially if you are aware of the security issues.

+ +

One overriding assumption was that your machine is secure, and +that the serious threats were those of eavesdroppers on the network +and e-mail forgers. In general, premail handles passive attacks quite +well, while containing a number of vulnerabilities to active attacks. +

+ +

Here are some potential security pitfalls with premail:

+ +
    + +
  • Stores secrets information on disk file. + +
  • Stores (potentially sensitive) temporary files on disk. + +
  • Does not check authenticity of remailer list, remailer public key +ring, or Mixmaster information gotten from the Web. + +
  • Accessing the Web signals when anonymous mail is about to be sent, +perhaps aiding traffic analysis. + +
  • Does not evaluate the trustworthiness of public keys used for +encryption and signature checking. + +
+ +

Useless features

+ +

Over the years, premail has accumulated a number of features of +dubious value. One of them is support for MOSS, a nice encryption +protocol that nevertheless failed to catch on. If you feel the urge to +use it, documentation is available in the release +notes for version 0.43.

+ +

One potentially cool feature is a server for decoding e-mail. This +would be a useful feature if there were any mailers which used +it. The protcol for the server was designed to be fast (much, much +faster than invoking premail -decode separately for each +message), as well as "crypto-neutral," meaning that it doesn't contain +any features designed just for crypto, and that it could be used for +other tasks, for example converting image formats or character sets. +Thus, a client designed to use this protocol would like be fully +exportable from the US. If you're interested in integrating support +for this protocol into a popular e-mail client, please get in touch +with me.

+ +
+

Related documents

+
+ +
    + +
  • The README file for premail +version 0.33a. + +
  • Release notes for version 0.43 of premail. + +
+ +
+ +
premail home + + --- premail-0.46.orig/premail.1 +++ premail-0.46/premail.1 @@ -0,0 +1,1209 @@ +'\"macro stdmacro +.TH \f4premail\fP 1 "22 Aug 1997" "Premail Manual" "Premail Manual" +.ds OK [\| +.ds CK \|] +.SH NAME +premail \- An E\-Mail Privacy Package. Easy E\-Mail Encryption, Decryption, Signing and Anonymization. +.SH SYNOPSIS +.SS Command Line Invocations +.B \f4premail\fP +[ +.B -sendmail_options +] +.br +.B \f4premail\fP +.B -decode +[ +.B -body +] [ +.IR file +] +.br +.B \f4premail\fP +.B -makenym +[ +.IR nym@server .\|.\|.\| +] +.br +.B \f4premail\fP +.B -login +.br +.B \f4premail\fP +.B -logout +.br +.B \f4premail\fP +.B -setpass +.br +.B \f4premail\fP +.B -ripemkey +.br +.B \f4premail\fP +.B -importnym +[ +.IR nym@server .\|.\|.\| +] +.br +.B \f4premail\fP +.B -exportnym +[ +.IR nym@server +[ +.IR you@your.address +] ] +.SS Command Reference + +These are the things you put between the '((','))' on the To: line. +Note that all of these, at least in theory, can also be used as their own +header (first letter capitalized, of course). + +.TP +\f4encrypt-pgp\fP +Encrypts the message for the person(s) on the To: line. Synonymous with +\f4key\fP. \f4encrypt-pgp\fP = +.I name +encrypts for recipient +.I name. +.TP +\f4chain\fP +Chains through a number of remailers (default 3, if you want a different +number use \f4chain\fP = +.IR num +). If individual remailers are specified, they are placed +after the '=' rather than +.I num +and separated by ';'. A special case of this is +.I nym_server += +.I name +which chains through your +.I name@nym_server +pseudonym. Mixmaster remailers are specified by having one or more seperated +by ';' enclosed in an extra set of parentheses. Mimaster remailers cannot be +chained automagically yet. +.TP +\f4sign\fP +Signs your message, either with your default signature id (See "Preferences" +below) or with the user id given after an '='. +.TP +\f4Anon- Headers\fP +Not really a command, but any header in your message named Anon- +.I Foo +will come out of the last of a chain of remailers as +.I Foo +instead (i.e. the information will be preserved and the Anon- stripped). + + +.SH DESCRIPTION +.LP +The main function of \f4premail\fP is adding support for encrypted e-mail +to your mailer, using plain PGP, PGP/MIME, MOSS, or +S/MIME. + +In addition, \f4premail\fP provides a seamless, transparent interface to +the anonymous remailers, including full support for Mixmaster +remailers and the nymservers. Nymservers provide cryptographically +protected, fully anonymous accounts for both sending and receiving +e-mail. These are known as pseudonyms or persistent anonymous accounts. + +While \f4premail\fP can be used as a stand-alone application, it works +best when integrated with your mailer. Currently, \f4premail\fP is +integrated completely seamlessly and transparently only with +Netscape 3.0's built-in mailer. It works fairly well with Pine +3.94 or later, as well (plain PGP is supported, but decryption of +MIME-based e-mail encryption protocols is still missing). +Transparent integration of outgoing mail only is supported for any +mailer in which the mail sending program can be configured, +including Berkeley mail, most emacs mailers, MUSH, and MH. For these +mailers, you can decode messages with a single command. + +To integrate with your mailer, \f4premail\fP places itself between the +mailer and the actual mail transport. For outgoing mail, premail +masquerades as sendmail. You configure your mailer to call premail +instead of sendmail. Then, \f4premail\fP performs the encryption or +signing, and invokes sendmail to actually send the message. + +For mailers that call a command to receive incoming mail (including +Netscape 3.0), the situation is similar. Netscape, for example, can +be configured to call movemail to get incoming mail. To integrate +premail, you'd configure Netscape to call \f4premail\fP instead, which +would in turn call movemail to actually get the mail, then would +decode it. + +.SS Requirements + +You need the following software in order to effectively use +\f4premail\fP: + +.RS 2 + * Unix. Unfortunately, \f4premail\fP does not work on Mac or Windows. + * Perl 5.000 or later. + * PGP (version 2.6.2 recommended). + * RIPEM 3.0b3 or later (optional, for S/MIME support) + * TIS/MOSS 7.1 (optional, for MOSS support) + * Mixmaster (optional, for higher security anonymous mail) + * Lynx (only if you're behind a firewall) +.RE + +.SH USAGE + +.SS Command Line Invocation + +Hopefully, you have integrated premail into your mail client, and +you won't have to invoke it from the command line. However, there +may still be times when it is convenient to use premail from the +command line. + +The most basic use of premail is as a replacement for sendmail. For +example, you can send mail directly from the command line, as +follows (here, the > represents the Unix prompt): + + > premail -t + To: raph@cs.berkeley.edu ((sign)) + Subject: premail bug report + + Here's a bug in premail: ... + . + > + +The -t option specifies that the recipients are extracted from the +header fields (To:, Cc:, Bcc:, and the Resent- variants of each). +As in sendmail, you can specify the recipients on the command line +instead of using the -t option. + +In addition, you can set configuration options from the command +line, using the +option=value syntax. This is especially useful +with the debug option. For example, to show you what happens +when formatting mail for remailers, but not actually send the +message: + + + > premail +debug=ry -t + To: raph@cs.berkeley.edu ((chain=1)) + Subject: test of remailer + + test + . + Chose chain exon + /usr/lib/sendmail -oi remailer\@remailer\.nl\.com + +There is one configuration option that can only be set from the +command line in this fashion, which is the location of the preferences +file itself. The configuration option is preferences, and the +default value is ~/.premail/preferences. You could, of course, +alias premail to have this option always set. + + +.SS Encryption + +Once you've got premail set up, actually using encryption is easy. +You simply add commands in double parentheses to the e-mail +addresses. The encrypt-pgp command (which can be abbreviated to +key) adds encryption to the outgoing mail, and the sign command +signs it. + +For example, to send me encrypted mail, you'd send it to +raph@cs.berkeley.edu ((encrypt-pgp)). You need to have a key with +this user id on your PGP public keyring, otherwise you'll get an +error message. If the user id on the key doesn't match the e-mail +address, you can specify it directly. For example, to send mail +directly to my workstation, but using the same public key as above, +use raph@kiwi.cs.berkeley.edu ((key=raph@cs.berkeley.edu)). + +Signing works much the same way. I can sign mail by adding +((sign=raph@cs.berkeley.edu)) to the outgoing address. Actually, +because I set the signuser configuration option in my preferences +file, all I have to add is ((sign)). + +Doing both encryption and signing is just as easy. For example, to +send me signed, encrypted mail, use this line: + + To: raph@cs.berkeley.edu ((encrypt-pgp, sign)) + +Each recipient is treated separately - the double-paren commands +after an e-mail address apply to that recipient only. However, you +can add a Sign: header field to indicate that your message is +signed for all recipients. Example: + + To: vp@company, secretary@company, employees@company, + friend@outside ((encrypt-pgp)) + Subject: Important announcement + Sign: + + ... + +In this example, all recipients will get a signed message, and the +message to friend@outside will be encrypted as well. + +.SS Decoding + +The basic way to decode encrypted messages is to use premail +-decode as a command line. You can either give a filename as an +argument, or premail will accept the encrypted message on its +standard input. In either case, the decoded message will be printed +on the standard output. + +The message can be a standard e-mail message (RFC 822 format), or +it can be an entire mailbox. In the latter case, premail will +decode each of the messages individually. If you don't have premail +directly integrated into your mailer, then here's a handy way to +view your mail: + + premail -decode $MAIL | more + +If the message is actually encrypted, then premail will need to +access the secrets file. If you are logged out of premail, then +premail will try to open an xterm window for you to type the +passphrase for the secrets file. If that doesn't succeed, premail +will print an error message. At that point, you might choose to log +in (i.e. premail -login) and then try the decoding again. + +If, as in many mailers, you have easy access to the body of the +message but not the header, then you can use premail -decode -body +on the body. This works well for plain PGP encrypted messages, but +unfortunately does not work for MIME-based message formats, because +important information is contained in the header. + +The results of the decoding (including signature verification) are +given in an X-Premail-Auth: header field. This header field is +protected against forgery; if the original message contains it, it +is changed to X-Attempted-Auth-Forgery. + +.SS Anonymity + +The original reason for writing premail was to provide good support +for anonymous remailers. If you're not interested in sending +anonymous mail, you can skip this section. + +Sending anonymous mail is very similar to sending encrypted mail. +Simply add the ((chain)) command to the recipient's e-mail address. +Alternatively, you can add a Chain: header field, and the mail will +be send anonymously to all recipients. + +Even though the chain command is simple, a lot is going on under +the surface. The default chain is 3, which asks that three "good" +remailers be chosen randomly. To make sure that it makes its choice +based on fresh, up-to-date information, premail downloads the +remailer list and a set of PGP public keys for the remailers from +the Web (the actual URLs are configuration options). After choosing +the remailers, the message is multiply encrypted with the PGP +public keys, and finally sent to the first remailer in the chain. + +The automatic chain selection process is very good. My tests +indicate that reliability is consistently above 99%. Further, the +chain selection process avoids some potential problems. For +example, some remailers are known not to work well in chains, +probably because of incorrectly configured "block lists." Also, +some remailers are "linked," in the sense of being hosted on the +same machine, or being administered by the same person. Choosing a +sequence of linked remailers wouldn't offer much security, so +premail doesn't. + +You can also choose the chain length. A shorter chain will be +faster and more reliable, but less secure, and conversely for +longer chains. For example, ((chain=5)) selects a chain of five +remailers. + +If this isn't enough control, you can specify the exact chain of +remailers by hand. For example, ((chain=replay;jam;exon)) bounces +the message around a few times outside the US. + +Mixmaster chains are specified inside an additional set of +parentheses. At the moment, there is no way to automatically select +a chain of Mixmaster remailers, so you have to do it by hand. For +example: ((chain=(replay;ecafe-mix;lcs))). You can even mix +Mixmaster and type-1 remailers; for example, +((chain=(anon);1;(replay))) will sandwich one well-chosen remailer +between the two Mixmaster remailers. + +Extra header fields can be placed in the outgoing message by +prefixing the header with "Anon-". A particularly common usage is +an Anon-Reply-To: field, which specifies a reply-to address in the +mail delivered to the recipient. The Reply-To: header field is used +often enough that premail includes a default-reply-to configuration +option, which automatically adds it to all anonymous messages. + +The following header fields are passed through to the anonymized +message, even without the Anon- prefix: + + Mime-Version: + Content-Type: + Content-Transfer-Encoding: + Newsgroups: + X-Anon-To: + In-Reply-To: + References: + +.SS Using Nyms + +This section describes how to create and use _nyms_, which are +accounts for sending and receiving anonymous mail. There are two +types of nymservers: alpha (named after the now defunct +alpha.c2.org), and newnym. For the most part, the operation of the +two is similar. + +To create a new nym, type + + premail -makenym + +and follow the prompts. This command is also good for updating an +existing nym, which is important if one of the nym's remailers goes +down. + +You can also create or update a nym from the command line, as +follows: + + premail -makenym you@alias.cyberpass.net your@real.address chain fakechains + +Note that chain is the number of remailers to use. + +When premail creates a nym, it chooses random passphrases (one for +each remailer in the chain). The passphrases and other details of +the nym are stored in the premail secrets file. Thus, the nym is +fairly secure (much more so than, say, anon.penet.fi). + +The decode mechanism handles responses to nyms, again looking up +the passphrases in the premail secrets file. + +You can also send mail from your nym, in one of two ways. Assume +for the sake of example that your nym is you@alias.cyberpass.net. +Then, you would use a chain of 2;cyber=you. Alternatively, you can +use a chain of 2;cyber and include this header field: + + Anon-From: you@alias.cyberpass.net (You Know Who) + +If you want the nymserver to send you a confirmation every time you +send mail from your nym, add a $config{'ack'} = 'yes'; line to your +preferences file. + +To delete a nym: + + premail -makenym you@alias.cyberpass delete + +Please delete nyms if you are not actually using them; this helps +free up disk space and prevents the nymservers from being +overloaded. + +As of version 0.45, premail now supports the newnym type of +nymserver. This nymserver is more richly featured than the alpha +type. You do have to answer a few more prompts when creating nyms +for the newnym type, including creating a new PGP key. It's worth +it, though. The newnym servers seem to be working a lot better than +the alpha ones ever did. For more information on newnym, see the +nym.alias.net homepage. If you want to exchange nyms between +premail and other programs (or a manual setup), then take a look at +the -importnym and -exportnym commands, which are explained in the +documentation for the patch that upgraded premail 0.44 to have +newnym capability. + +From the patch documentation: + +.RS 3 +There are two new premail commands for dealing with "newnym"-style +nyms (such as those on nym.alias.net), "-importnym" and "-exportnym". + +If you have an existing nym on nym.alias.net and you want to switch +over to premail for managing that nym, run the command "premail +-importnym". This will behave like "premail -makenym" except that it +will use a PGP key already on your PGP keyring rather than creating a +new PGP-key for the nym. Be aware, however, that premail will change +your remailer chain and shared-key encryption passwords, so you will +have to decrypt all subsequent mail you receive with premail. (The +PGP key won't change, so if you don't like premail, you can always +change back by manually mailing in a new reply-block.) + +Finally, if you created a nym with premail but would like to switch to +something else, you can export your nym's PGP key by running "premail +-exportnym". This will put your nym's public and private keys in the +/tmp directory. The private key is not protected by a password, so +you will probably want to edit it with "pgp -ke" before adding it to +your private keyring. +.RE + +.SS Posting To Usenet + +Even though some remailers can post directly to Usenet, premail +does not support that. Thus, if you want to post to Usenet, you +should use a mail-to-news gateway. + +To find a working mail-to-news gateway, check Don Kitchen's +list. There are two basic kinds: sites that scan the header +fields, and sites that include the newsgroup in the address. + +Using the address-parsing kind, to post to alt.anonymous, you'd +just send mail to alt.anonymous@myriad.alias.net (assuming, of +course, that myriad.alias.net is still functioning). + +Using the header-scanning kind, send mail to +mail2news@myriad.alias.net, and include this header field: + + Newsgroups: alt.anonymous + +The header scanning kind has one advantage: you can cross-post to +multiple newsgroups using one mail message. If you post to multiple +newsgroups, make sure you don't put a space between the newsgroups, +only a comma. Otherwise, the articles will bounce. + +One frequently asked question is: how can I follow up on a thread +while posting anonymously? This is easy. Find the Message-Id: +header field in the post you're responding to, and change it into a +References: field in your outgoing mail. + +Here's an example that ties it all together. Let's say you wanted +to reply to this post: + +.RS 2 + From: Edward Brian Kaufman + Newsgroups: alt.privacy.anon-server,alt.anonymous + Subject: A few questions about anon posts + Message-ID: + + Hi, + + I'd like to know what the best/easiest way to do anon posts is and + how to do them. Thank you, + + Ed +.RE + +To post the reply anonymously, send this mail: + +.RS 2 + To: mail2news@myriad.alias.net ((chain)) + Cc: Edward Brian Kaufman ((chain)) + Newsgroups: alt.privacy.anon-server, alt.anonymous + Subject: Re: A few questions about anon posts + References: + + If you have a Unix machine, using premail is the best way. To find + out how, read the manual. +.RE + +.SS S/MIME + +Version 0.45 of premail contains limited support for S/MIME +messages. Basic message formatting works, but there are problems +with creating usable certificates, and there is still no support +for an encryption algorithm interoperable with RC2. However, a few +hearty souls may wish to experiment with the S/MIME functionality +that is present. This section explains how to do it. + +First, you must install RIPEM 3.0b2 (or later). This is available +from the ripem export-controlled FTP site. You'll need to get +an account on the server in order to download any of the +export-controlled code - the GETTING_ACCESS file on the site +explains how. + +Once you have RIPEM installed (and the ripem configuration option +pointing to the executable), create a public key with this command: + + premail -ripemkey + +You will then be prompted for your e-mail address. Alternatively, +you can give your e-mail address as a command line argument to +premail -ripemkey. + +After your key is created, you can send signed messages by adding +the ((ssign)) command. If you send a signed message to another +premail user, they will have your public key, and can send you +mail, by using ((encrypt=your@user.id)). + +The default encryption is Triple-DES. If the recipient can't handle +it, then ((encrypt-des)) will fall back to plain DES, which most +users will be able to decrypt - probably including "export" +versions of S/MIME. Of course, the disadvantage of using plain DES +is that any competent spy organization will also be able to decrypt +the messages ;-). + +Unfortunately, RIPEM 3.0b2 has some significant differences from +other S/MIME implementations in the way it handles public key +certificates. These prevent you from getting a VeriSign certificate +you can use. It is, however, possible to accept VeriSign class 1 +beta certificates by running the following (prompts and messages +are in normal font, what you type is in boldface; you can find out +the password by looking in the secrets file): + + > _rcerts -u your@user.id_ + Enter password to private key: + E - Enable standard issuers... + \f2...other choices...\fP + Enter choice: + \f2e\fP + ...V - VeriSign something or other... + \f2v\fP + Enter the number of months the certificate will be valid, or blank to + cancel: + \f212\fP + Enter choice: + \f2q\fP + +.SH SETUP + +.SS Installation + +First, you need to get premail. The source code is available from +an export-control Web server. You may also be able to find a +copy on the Hacktic FTP site in the Netherlands. In either +case, you want to get the file premail-0.45.tar.gz. + +After you've gotten the file, unpack it. This command should do it: + + gzip -dc premail-0.45.tar.gz | tar xvf - + +The unpacking process will create a subdirectory called +premail-0.45, containing the following files: + +.TP +README +A short description of the contents +.TP +premail +The premail program itself +.TP +preferences +A skeletal preferences file +.TP +doc.txt +This document in ASCII format. +.TP +doc.html +This document in html format. + +.LP +Test to see if you can run premail. These commands should print a +usage summary: + + cd premail-0.45 + ./premail + +If you get an error message reading "command not found," then you +will have to edit the first line of premail to refer to the actual +pathname of the perl5 interpreter. One good way to find out the +pathname is to do "which perl5" or "which perl". + +On the other hand, if you get a string of syntax errors, then the +problem is probably that you are running perl4, while premail needs perl5. +Try to see if you can find perl5 on your machine. Otherwise, you +may need to install perl5 yourself. + +If you will be using premail from the command line frequently, then +you may want to copy (or symlink) the premail program into a +location in your $PATH. For example, if you have permission to add +files into /usr/local/bin, then you may consider running this +command: + + cp -p premail /usr/local/bin + +An easier way may simply be to make a directory $HOME/bin, put premail +in there, and add that to your $PATH. You could, of course, also try +bugging the sysadmin at your site to install it for you into a pulically +available location (like /usr/local/bin as above). + +At this point, you are ready to test whether premail actually +works. We are assuming that you already have PGP installed and have +generated your own public key. Type this command, substituting in +your own e-mail address: + +.RS 2 + ./premail -t + To: your@own.email.addr ((encrypt-pgp)) + Subject: Test + + Does this really work? + . +.RE + +If all goes well, you should be back at the command line within a +couple of seconds. If it seems to hang without any disk or net +activity, try typing randomly for a minute, under the assumption +that PGP needs random keystrokes. This shouldn't happen if PGP is +already set up correctly (including having generated your own +public key), but on the chance that it isn't, hanging while waiting +for random keystrokes is one of the more common failure modes. + +This is also the point at which you may get a PGP error. Two common +problems are that premail can't find the PGP program, in which case +you will want to add a line to your preferences file (see +"Preferences" below), or that it can't find the public key corresponding to +your e-mail address. + +If the test was successful then you should now have a PGP-encrypted message in +your mailbox. + +.SS The Secrets File + +To create signatures, decrypt messages, or use nyms, you need to +set up a "premail secrets" file. If you will only be using premail +to encrypt outgoing mail, you can skip this section. + +The default filename is /tmp/.premail-secrets.$< , where $< is +equal to your numeric user id. To change the filename, use a +preferences line such as this one: + + $config{'premail-secrets'} = '/mnt/cryptdisk/premail-secrets'; + +If you don't know your numeric user id, you can find it by running +"echo $uid" (from csh or tcsh), "echo $UID" (from sh or bash), or: + + perl -e 'print "$<\n"' + +The premail secrets file has this format: + +.RS 2 + $pgppass{'user'} = 'PGP passphrase for user'; + $pgppass{'alternate'} = 'PGP passphrase for alternate'; +.RE + +However, make sure your premail secrets file has restrictive +permissions, so other people on your system can't read your +passphrases! This command is well recommended (substituting your +actual user id, of course): + + chmod 600 /tmp/.premail-secrets.7437 + +.SS Logging In and Out of Premail + +Generally, premail stores its secrets file in the /tmp directory. +In some cases, this is good enough security. In other cases, it +might be better to store the file encrypted most of the time, and +only decrypt it when necessary. To use this capability of premail, +first set a passphrase with: + + premail -setpass + +You will be prompted for a passphrase. You can use the same +passphrase as for your PGP key, or a different one, depending on +how many passphrases you want to remember. This command leaves you +logged in with the new passphrase set. + +To log out: + + premail -logout + +You might consider adding this command to your .logout file, so +that it occurs automatically every time you log out of your +account. + +To log in again: + + premail -login + +If you are running on a system with X, then premail will +automatically pop up a window to log in whenever the secrets are +needed. If you are not running X, and the secrets are needed, you +will get an error. In this case, you can log in manually and try +the command again. + +.SS Preferences + +While premail's default configuration is designed to be sufficient +for the the most common cases, you may want to change some of the +configuration options. This is done by adding lines to the +preferences file. + +The default location for the preferences file is +~/.premail/preferences, where ~ represents your home directory. The +premail distribution comes with a skeleton preferences file, but it +does not automatically copy it into the ~/.premail directory. You +might choose to do that yourself, or you might create one from +scratch. + +The format of the preferences file is a sequence of lines such as +the following: + + $config{'option'} = 'value'; + +All other lines (including those beginning with #) are considered +to be comments and are ignored. Here's a typical preferences file +(actually, the one on my home machine): + +.RS 3 +$config{'logfile'} = '/home/raph/premail/log'; +$config{'debug'} = 'chvl'; +$config{'movemail'} = '/home/raph/bin/movehome'; +$config{'ripem'} = '/home/raph/install/ripem/main/ripem'; +$config{'pgp'} = '/usr/local/bin/pgp'; +.RE + +As you can see, a major use for the preferences file is to specify +full pathnames for the helper programs. In addition, I've set it up +to produce a full log, which I find useful, because I'm constantly +tracking down bugs :-) + +Here's a table of all the configuration options, their defaults, +and a very brief description. More complete descriptions are found +in the preferences file included in the premail distribution. + +.TP +Option, Default +Explanation +.TP +pgp, pgp +The location of the PGP executable. +.TP +sendmail, /usr/lib/sendmail +The location of the sendmail executable. +.TP +mixmaster, mixmaster +The location of the Mixmaster executable (useful for more +secure anonymous mail). +.TP +movemail, movemail +The location of the movemail executable (useful for +integrating Netscape 3.0). +.TP +ripem, ripem +The location of the ripem executable (needed for S/MIME +messages). +.TP +mossbin, +The directory containing the TIS/MOSS executables (needed for MOSS +messages). +.TP +post, post +The location of the MH post executable (needed for MH +integration). +.TP +geturl, +A command for getting files from the Web. Use "lynx -source" if +behind a firewall. +.TP +dead-letter, ~/dead.letter +The file where premail stores undeliverable mail. +.TP +logfile, +The location where premail stores its log, if the l debug flag is +set. +.TP +storefile, +If set, the location where premail stores outgoing mail, instead of +calling sendmail. +.TP +tmpdir, /tmp +Where premail stores its temporary files. +.TP +charset, iso-8859-1 +The default charset for outgoing 8-bit messages. +.TP +encrypt, yes +Set to blank to disable PGP encryption to remailers. +.TP + ack, +If set, nymservers will send acknowledgements for all outgoing mail. +.TP +extrablank, +If set, premail adds an extra blank on remailer messages. Useful if +behind a broken mail proxy. +.TP +debug, +Debugging flags (see section on debugging). +.TP +signuser, +The user id of the default PGP secret key used to sign messages. +.TP +default-reply-to, +Adds a Reply-To: header field with this address when sending +anonymous e-mail. +.TP +addresses, ~/.premail/addresses +The file containing your addresses. +.TP +rlist, ~/.premail/rlist +The file where premail stores the remailer list. +.TP +pubring, ~/.premail/pubring.pgp +The file where premail stores the public +keyring for the remailers. +.TP +premail-secrets-pgp, ~/.premail/secrets.pgp +The file where premail stores the encrypted +secrets file. +.TP +premail-secrets, /tmp/premail-secrets.$< +The location of your secrets file +.TP +rlist-url, http://kiwi.cs.berkeley.edu/rlist +The URL for the remailer list. +.TP +pubring-url, http://kiwi.cs.berkeley.edu/pubring.pgp +The URL for the remailer +public keyring. +.TP +type2-list-url, http://www.jpunix.com/type2.html +The URL for the Mixmaster type2 +list. +.TP +pubring-mix-url, http://www.jpunix.com/pubring.html +The URL for the Mixmaster +pubring. + +.SS Address Book + +Adding the extra encryption commands is not difficult, but it can +be tedious and potentially error prone. Thus, premail provides an +address book for specifying commands to be used with specific +e-mail addresses. + +For example, let's say that one of your correspondents tells you +that she prefers mail to be PGP encrypted. Then, instead of typing +((encrypt-pgp)) every time you send her mail, you could add this +line to your addresses file: + + her@email.address: ((encrypt-pgp)) + +The addresses file is usually at ~/.premail/addresses, but the +location is a configurable option. + +Another example was the hackerpunks mailing list (now defunct), in +which all of the subscribers have alpha.c2.org nyms. Since +haqr@alpha.c2.org had this line in his addresses file, he was able +to post to the list with just "To: hpunks": + + hpunks: hackerpunks@alpha.c2.org ((chain=2;alpha=haqr)) + +An address book entry can also expand to a list of addresses. For +example: + +.RS 3 +alice: alice@crypto.com ((encrypt-pgp)) +bob: bwhite@got.net ((key=bobw@netcom.com)) +eric: eric@ecsl.org ((encrypt-pgp)) +.br +friends: alice, bob, eric +.RE + +Sending mail to friends would then do what you'd expect: send +encrypted mail to each of alice, bob, and eric's full e-mail +addresses. + +.SH INTEGRATION + +This section discusses integrating premail with various remailers. + +.SS Netscape + +Create symbolic links to premail called "prezilla" and +"premailmove". To do this, make sure you are in the same directory +as premail itself, and type: + + ln -s premail prezilla + ln -s premail premailmove + +Find a working movemail. If you have emacs installed, then you +most likely have one in /usr/lib/emacs/etc/movemail or a similar +location. If you don't already have one, then the source (or +possibly binary) for one is included in the Netscape Navigator +distribution and you can build it (no need if a binary is +included). Then, make sure premail can find it by adding a line +such as this one to your preferences file: + + $config{'movemail'} = '/usr/lib/emacs/etc/movemail'; + +This usage assumes that you get your mail from a mail spool, as +opposed to POP or some such. You may be able to get it to work for +POP as well, but you need to figure out how to invoke movemail to +move the mail from your mailbox to a file (specified as the second +argument to the movemail script). + +Add this line to your .cshrc, assuming your shell is csh or +tcsh: + + setenv NS_MSG_DELIVERY_HOOK /your/path/to/prezilla + +Also run this command from the shell so it takes effect +immediately. The syntax is slightly different if your shell is sh +or bash _(note: is this right? Yes, it is.)_: + + NS_MSG_DELIVERY_HOOK=/your/path/to/prezilla + export NS_MSG_DELIVERY_HOOK + +Start Netscape (exit first if it's already running). Go to the +Options|Mail and News Preferences dialog, select the Servers tab. +Click on "External Movemail" and set the value to +/your/path/to/premailmove. + +Try sending yourself mail, and clicking on "Get Mail" from the +Netscape Mail window. The mail should show up in the Inbox, +correctly decoded. + +To view the X-Premail-Auth: header field to see the result of +signature checking, select Options|Show All Headers from the +Netscape Mail window. + +Note: as of Netscape v3.0, there is still a bug in the handling of +the Bcc: header field, which causes it to be ignored. Do not use +this field. Hopefully, this will be fixed in a future version of +Netscape. + +Note: some 3.0 beta versions modify the PATH environment variable. +If premail seems to work correctly from the command line, but not +from Netscape, try setting absolute pathnames for the programs used +by premail. + +.SS Pine + +As of Pine 3.94, premail integrates both outgoing mail and the +decryption of plain PGP incoming mail. Unfortunately, decryption of +MIME-based mail is not yet supported. + +Two Pine configuration options need to be set to integrate premail +(i.e. from the main Pine screen, S for setup, then C for +configure). First, sendmail-path should be set to a value similar +to this (substituting the actual path to premail): + + /your/path/to/premail -oem -t -oi + +Second, display_filters should be set to a value similar to this: + +.RS 3 +_BEGINNING("-----BEGIN PGP")_ /your/path/to/premail -decode -body +.RE + +If you have trouble finding these options in the setup screen, then +you can edit the .pinerc file directly. + +One caveat when using Pine: it usually tries to be "smart" and +remove comments from e-mail addresses, which includes the +double-paren commands such as ((encrypt-pgp)). There are a few ways +to deal with this problem: + +.RS 2 + * Use "( )" instead of (( )). _Note: I think this works, but I +haven't tested it._ + * Use the alternative caret syntax. These two lines mean the same +thing: + + To: raph@cs.berkeley.edu ((encrypt-key, sign)) + To: raph@cs.berkeley.edu^encrypt-key^sign + * Avoid setting the encryption options on the command line +altogether, and set them in the addresses file instead (see +"The Address File"). You could also use the header forms. +.RE + +.SS MUSH + +Premail integrates well with the Mail User's Shell. Add the following lines to your .mushrc: + +.RS 3 +set sendmail='premail -oem -i -t #Comment' +cmd decode 'pipe !* premail -decode >>$MAIL;delete !*' +.RE + +Outgoing mail will be handled automatically. Note that if you are sending +anything with a ';' on the mush command line, it must be enclosed in "'". For +example: + +.RS 3 +mail user@host ((chain=replay;hacktic)) +mail 'user@host ((chain=replay;hacktic))' +.RE + +The first line above will fail, use the second line instead. + +For outgoing mail, simply type 'decode [msg-list]'. It will decode those +messages, append them to the end of your mailbox. You will be notified of the +new mail. Note that this occurs even with those messages in the list that +premail does nothing to. Since no update has been done, you can use +undelete to look at the old (pre-premail) versions of the messages, +but when you quit they'll be tossed. + + +.SS Other mailers + +This section describes how to integrate premail with MH, emacs, and +UCBMail. With these mailers, premail will only handle outgoing mail +automatically. To decode incoming mail, you still need to invoke +premail -decode by hand. + +.SS Integrating premail with Emacs + +To add premail support to emacs, just add this line to your .emacs +file: + + (setq sendmail-program "/your/path/to/premail") + +.SS Integrating premail with MH + +In whatever directory you keep the premail executable, create a +symbolic link as follows: + + ln -s premail prepost + +Under the name "prepost", premail will masquerade as MH's post +program rather than sendmail. You can get MH to call premail +instead of post by adding this line to your .mh_profile: + + postproc: /your/path/to/prepost + +One thing to keep in mind is that premail's processing is done +before that of post. Thus, if you have MH aliases, they will get +expanded after the call to premail. If you use only premail +aliases, only MH aliases, or neither, this won't be a problem. + +Alternatively, if you have appropriate privileges, you can add this +line to /usr/lib/mh/mtstailor: + + sendmail: /your/path/to/premail + +You may also have to configure MH to call sendmail locally rather +than connecting to an SMTP server. Don't do both the mtstailor and +mh_profile methods -- that would run premail twice. + +.SS Installing premail with UCBmail + +UCBmail is a simple mailer front-end (also known as Mail and +mailx). If, when you type "mail user@site.dom", the mailer asks you +for a "Subject: " line, you are undoubtedly using UCBmail. If so, +you are in luck - it integrates very easily with premail. Just add +this line to your ~/.mailrc file: + + set sendmail=/your/path/to/premail + +Using premail with UCBmail is not very different from using premail +by itself, but you do get some handy features, such as including +files and using an editor on the mail. + +.SH NOTES + +This section covers a number of techincal notes related to the +operation of premail. This information should not be necessary for +ordinary use. + +.SS Multiple recipients + +One of the tricky problems with mail encryption packages such as +premail is how to deal with multiple recipients. Based on +experience with previous versions, this version of premail tries +very hard to "get it right." However, as a consequence, the exact +behavior can sometimes be difficult to understand. + +The hard part is when some of the recipients have encryption +specified and others don't. What premail does is to split the +recipients up into groups. If two recipients can receive the same +actual message, they are in the same group, otherwise not. For +example, recipients getting an encrypted and an unencrypted message +cannot be in the same group. However, multiple recipients appearing +in To: and Cc: fields that use the same encryption method will be +in the same group. A single message, encrypted to multiple +recipients, will be sent, which is considerably more efficient than +encrypting separately for each recipient. + +One subtle point is the handling of Bcc: recipients. The semantics +of Bcc: specify that the mail be sent to each of the Bcc: +recipients, but that none of the other recipients be able to find +out their identity. However, encrypting to multiple recipients +would defeat this, because it is possible to indentify all of the +recipients of the encrypted message. Thus, each encrypted Bcc: +recipient gets its own group. + +Each recipient of an anonymous message also gets its own group, for +similar reasons. + +An attempt is made to make the headers in the message received by +the recipient be the same as if no encryption were used. +Specifically, the complete To: and Cc: header fields will be +present, but the Bcc: field will be missing. One exception to this +rule is anonymous messages, in which case the recipient can't see +any information about the other recipients. + +.SS Error handling + +The goal is to handle errors in the same way as sendmail. Thus, the +exact handling depends on the setting of the -oe command line +option. The default (as in sendmail) is -oep, meaning that the +error message is printed to standard out, and the mail message is +appended to the dead letter file (the location of which is a +configuration option). + +Another choice is -oem, in which case the error message and the +mail message are packaged together and mailed back to the user. +This is appropriate when the mailer has no way to deal with error +messages returned from premail. + +One additional choice, not provided by sendmail, is -oed, which +prints the error message on standard out, but drops the mail +message. This is a good choice if the mailer can interpret a +non-zero return status code as indication of an error. This is the +mode used by Netscape (and is automatically selected when premail +is invoked as prezilla). + +.SS Security issues + +In designing premail, usefulness and convenience were considered +more important than top security. Nonetheless, it can provide good +security, especially if you are aware of the security issues. + +One overriding assumption was that your machine is secure, and that +the serious threats were those of eavesdroppers on the network and +e-mail forgers. In general, premail handles passive attacks quite +well, while containing a number of vulnerabilities to active +attacks. + +Here are some potential security pitfalls with premail: + +.RS 2 + * Stores secrets information on disk file. + * Stores (potentially sensitive) temporary files on disk. + * Does not check authenticity of remailer list, remailer public key +ring, or Mixmaster information gotten from the Web. + * Accessing the Web signals when anonymous mail is about to be sent, +perhaps aiding traffic analysis. + * Does not evaluate the trustworthiness of public keys used for +encryption and signature checking. +.RE + +.SS Useless Features + +Over the years, premail has accumulated a number of features of +dubious value. One of them is support for MOSS, a nice encryption +protocol that nevertheless failed to catch on. If you feel the urge +to use it, documentation is available in the release notes for +version 0.43. + +One potentially cool feature is a server for decoding e-mail. This +_would_ be a useful feature if there were any mailers which used +it. The protcol for the server was designed to be fast (much, much +faster than invoking premail -decode separately for each message), +as well as "crypto-neutral," meaning that it doesn't contain any +features designed just for crypto, and that it could be used for +other tasks, for example converting image formats or character +sets. Thus, a client designed to use this protocol would likely be +fully exportable from the US. If you're interested in integrating +support for this protocol into a popular e-mail client, please get +in touch with me. + +.SH Debugging + +If you run into trouble with premail, it might be of value to turn +on some of the debugging options. This can be done on the command +line, or in the .premailrc file. In the former case, add a ++debug=chvy argument to the command line. In the latter case, try: + + $config{'debug'} = 'chvy'; + +Here are the meanings of the debug options: + + c: Print command line invocation. + h: Print headers of input message. + l: Debug output goes to log instead of stdout. + p: Print finished message, do PGP. + r: Print chain chosen (useful in debugging chain selection). + y: Print finished message, don't do PGP. + v: Print all kinds of verbose info. + +Note that +debug=p puts the encrypted message on stdout. This may +be useful for constructing reply blocks, among other things. + +If there are problems with premail, then one of the best ways to +track them down is through the log. Try setting the debug +configuration option to chvl, setting the logfile configuration +option (for example, to ~/.premail/log), and then examining the +log. Also, if you're bringing bugs to my attention, it helps a lot +if you can send me relevant excerpts from the log. + +.SH SEE ALSO + +This document is available online at +http://www.c2.net/~raph/premail/. + +This is the documentation for premail 0.45. + --- premail-0.46.orig/premail-orig +++ premail-0.46/premail-orig @@ -0,0 +1,6621 @@ +#!/usr/bin/perl5.004 +#!/usr/bin/perl -w <--- It's just to buggy for this :-( +# +# premail, an e-mail privacy package +# +use FileHandle; + +$version = '0.46'; + +# Copyright 1996,1997 Raph Levien +# All rights reserved. +# +# This program is free for commercial and non-commercial use as long as +# the following conditions are adhered to. +# +# Copyright remains Raph Levien's, and as such any Copyright notices in +# the code are not to be removed. If this package is used in a product, +# Raph Levien should be given attribution as the author of the parts of +# the program used. This can be in the form of a textual message at +# program startup or in documentation (online or textual) provided with +# the package. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the copyright notice, +# this list of conditions and the following disclaimer. +# +# 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. +# +# 3. All advertising materials mentioning features or use of this +# software must display the following acknowledgement: This product +# includes software developed by Raph Levien . If more +# than one author is so cited, the list may be combined into one +# sentence. +# +# 4. Use and adaptation of small, specific components of this software +# is actively encouraged, and is exempt from the requirements above. +# +# This software is provided by Raph Levien ``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 the author or contributors 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. +# +# The license and distribution terms for any publically available +# version or derivative of this code cannot be changed. i.e. this code +# cannot simply be copied and put under another distribution license +# [including the GNU Public License.] +# +# The reason behind this being stated in this direct manner is (Eric +# Young's) past experience in code simply being copied and the +# attribution removed from it and then being distributed as part of +# other packages. This implementation was a non-trivial and unpaid +# effort. + +# Both of the following is needed to have secure tempfiles. -Joey +use IO::Handle; + +unless (defined(&O_RDWR)) { + require Fcntl; + import Fcntl qw/O_RDWR O_CREAT O_EXCL/; +} + +# default configuration options + +$config{'pgp'} = 'pgp'; +$config{'mixmaster'} = 'mixmaster'; +$config{'movemail'} = 'movemail'; +$config{'ripem'} = 'ripem'; +#$config{'getmailers'} = 'finger remailer-list@kiwi.cs.berkeley.edu'; +#$config{'geturl'} = 'lynx -source'; + +#$config{'premailrc'} = '~/.premailrc'; +#$config{'remailers'} = '~/.remailers'; +$config{'preferences'} = '~/.premail/preferences'; +$config{'addresses'} = '~/.premail/addresses'; +$config{'rlist'} = '~/.premail/rlist'; +$config{'pubring'} = '~/.premail/pubring.pgp'; +$config{'premail-secrets-pgp'} = '~/.premail/secrets.pgp'; +$config{'dead-letter'} = '~/dead.letter'; +$config{'premail-secrets'} = '/tmp/.premail-secrets.$<'; +$config{'tmpdir'} = '/tmp'; + +$config{'rlist-valid'} = 300; + +$config{'rlist-url'} = 'http://www.cs.berkeley.edu/rlist'; +$config{'pubring-url'} = 'http://www.cs.berkeley.edu/pubring.pgp'; + +$config{'rlist-url'} = 'finger:rlist@anon.lcs.mit.edu'; +$config{'pubring-url'} = 'finger:pubring@anon.lcs.mit.edu';$config{'type2-list-url'} = 'http://www.jpunix.com/type2.html'; +$config{'pubring-mix-url'} = 'http://www.jpunix.com/pubring.html'; +$config{'type2-list-url'} = 'http://www.jpunix.com/type2.html'; +$config{'pubring-mix-url'} = 'http://www.jpunix.com/pubring.html'; + +$config{'charset'} = 'iso-8859-1'; + +$config{'encrypt'} = 'yes'; + +my @RELAYS; +#@RELAYS = ('anon.lcs.mit.edu'); + +# the following config options are for testing only! +#$config{'debug'} = 'chvy'; +$config{'debug'} = ''; # Turns off troublesome warnings -- bfulgham 7/1/99 + # This is a hack, suggested by Ewen McNeill to handle + # some valid error messages that would take too much + # effort to fix ATM. These errors should not impact + # day-to-day use of the program. +# Global state + +%cmdline_configs = (); # config options set from command line + +$post = 0; # masquerading as MH post? +@cmdline_recips = (); # command line recipients +$dasht = 0; # -t on cmd line +@post_args = (); # args passed through to MH post +@sendmail_args = (); # args passed through to sendmail + +$dashbs = 0; # invoked in smtp mode +$edit = 0; # invoked in edit mode +$editfile = ''; # name of file to edit +$dashoi = 0; # -oi on cmd line +$more_input = 1; +$in_active=0; # IN handler opened +$header_sep = ''; +$in_body = ''; # the filename of the input message body +$prezilla = 0; # special mode for Netscape Navigator 2.1 +@in_headers = (); # the headers of the input message, verbatim +$resent = 0; # treat message as resent? +@recips = (); # all recipients, full addresses +%alias = (); # alias table, from addresses +%ealias = (); # expanded aliases, keys are stripped + +@send_headers = (); # headers to send with message +%which_header = (); # which header each recipient "came from" +%header_premail_com = (); # premail commands from headers + +@groups = (); # all groups +%group_recips = (); # recipients in each group +%recip_group = (); # group for each recipient, keys are stripped + +@deliver_headers = (); # headers used to deliver message + +@anon_headers = (); # headers to add to anon messages only + +@links = (); # linkage groups of remailers + +$tmpfile_count = 0; +@open_tmpfiles = (); +%tmpfile_refcnt = (); + +$pgp_tmpdir = ''; + +$interactive = 0; +$error_mode = 'p'; # m = mail, d = display, s = smtp, g = gist + # p = print, and write dead.letter + +# main +{ +# &set_configs (); +# while () { +# chop; +# print (join (':', &strip_caret ($_))."\n"); +# } +# exit 0; + +# &set_configs (); +# &get_remailer_pubring (); +# while () { +# chop; +# if (&open_web ($_)) { +# while () { +# print; +# } +# close (WWW); +# } +# } +# exit 0; + +# ($base, @params) = &split_mime_params ($ARGV[0]); +# print "$base ".join (' ', @params)."\n"; +# ($val, $present) = &get_mime_param ('charset', @params); +# if ($present) { +# print $val."\n"; +# } +# exit 1; + &bail_sendmail (); + umask 077; + srand; + &parse_command_line (@ARGV); + &set_configs (); +# &getfile_from_web ("test", "http://kiwi.cs.berkeley.edu/~raph/remailer-list.html"); + if ($config{'debug'} =~ /c/) { &pdebug (join (' ', $0, @ARGV)."\n"); } + while ($more_input) { + $more_input = 0; + if (&open_input ()) { + &get_header ('-', '', 1); +# foreach $field (@in_headers) { +# print "--- [\n"; +# print $field; +# print "] ---\n"; +# } + &clear_alias (); + &find_recips (); + &pdv (&format_header ("Recipients", @recips)); + &prepare_send_header (); +# print "\n"; +# print @send_headers; + foreach $recip (@recips) { + $stripped = &strip_address ($recip); +# print &format_header ("Header of $recip is", +# $which_header{$stripped}); + } + &compute_groups (); + if ($#groups >= 1 || $error_mode =~ /^[mp]$/) { + if ($edit && !$prezilla) { + &error ("Edit mode can only handle one group\n"); + } + $n = $#groups + 1; + if ($error_mode =~ /^[mp]$/) { $n++; } # In case of error + $in_body = &prepare_for_n_passes ($in_body, $n); + } + foreach $group (@groups) { + &pdv ("Group: $group\n"); + &pdv (&format_header (" recipients", + &split_commas ($group_recips{$group}))); + &send_group ($group); + } + &close_input (); + } + } + &delete_open_tmpfiles (); +} + +sub bin_sendmail { +# Return the name of the real sendmail executable + if (!defined $config{'sendmail'} || $config{'sendmail'} eq '') { + # Standard place + (-x '/usr/lib/sendmail') && return '/usr/lib/sendmail'; + # Newer BSD-based systems + (-x '/usr/sbin/sendmail') && return '/usr/sbin/sendmail'; + # Okay, I give up + &error ("can't find path to sendmail\n"); + } else { + return &tilde_expand ($config{'sendmail'}); + } +} + +sub bail_sendmail { +# Bail to sendmail if we are being invoked as one of the sendmail aliases + + if ($0 =~ /(mailq|newaliases|smptd)$/) { + # out of our league, let the real sendmail take over + exec (&bin_sendmail (), @_); + } +} + +sub parse_command_line { +# &parse_command_line (@argv) +# Parse the command line, placing results in global state. + + if ($0 =~ /post$/) { + $post = 1; + } elsif ($0 =~ /edit$/) { + $edit = 1; + if ($#_ < 0) { &error ("edit needs an argument\n"); } + $editfile = shift; + } elsif ($0 =~ /zilla$/) { + $edit = 1; + $prezilla = 1; + $error_mode = 'd'; + if ($#_ < 0) { &error ("prezilla needs an argument\n"); } + $editfile = shift; + &add_terminating_newline ($editfile); + } elsif ($0 =~ /move$/) { + &move (@_); + } elsif ($0 =~ /decode$/) { + &decode (@_); + } elsif ($0 =~ /decodebody$/) { + &decode ('-body', @_); + } elsif ($#_ == -1) { + &usage (); + } + # handle special commands + while ($#_ >= 0) { + $_ = shift; + if (/^\-post$/) { $post = 1; } + elsif ($post && (/^\-(alias|filter|library|width|idanno|deliver)$/ + || /^\-(client|server|fill\-in$|partno)/)) { + # list of keywords obtained from MH 6.8.3 post.c + # parsing of MH options requires more fullness. For example: + # -library sets mail folder to + push (@post_args, $_); + if ($#_ < 0) { &error ("$_ option needs an argument\n"); } + push (@post_args, shift); + } elsif ($post && (/^\-(check|nocheck|debug|dist|encrypt|noencrypt)$/ + || /^-(nofilter|format|noformat|mime|nomime|msgid|nomsgid)$/ + || /^-(verbose|noverbose|watch|nowatch|whom|mail|saml|send)$/ + || /^-(soml|snoop|fill\-up|queued)$/)) { + # list of keywords obtained from MH 6.8.3 post.c + push (@post_args, $_); + } elsif ($post && /^-help/) { + print "This is premail, masquerading as post. It takes the same\n"; + print "options as post, but performs encryption and remailer". + " chaining as well.\n"; + if ($config{"post"}) { + print "For help on MH post, type $config{'post'} -help\n"; + } else { + print "For help on MH post, type /usr/lib/mh/post -help\n"; + } + exit 0; + } elsif (/^\-edit$/) { + $edit = 1; + if ($#_ < 0) { &error ("$_ option needs an argument\n"); } + $editfile = shift; + } elsif (/^\-oe(.)$/) { + $error_mode = $1; + if ($1 =~ /^[mwpqe]$/) { push (@sendmail_args, $_); } + } elsif (/^\-od(.)$/) { + push (@sendmail_args, $_); + } elsif (/^\-[BNRV].+$/) { + push (@sendmail_args, $_); + } elsif (/^\-[BNRV]$/) { + if ($#_ < 0) { &error ("$_ option needs an argument\n"); } + push (@sendmail_args, $_); + push (@sendmail_args, shift); + } elsif (/^\-f$/) { + if ($#_ < 0) { &error ("$_ option needs an argument\n"); } + shift; # discard + } elsif (/^\-t$/) { $dasht = 1; } + elsif (/^\-oi$/) { $dashoi = 1; } + elsif (/^\-b(.+)$/) { + if ($1 eq "s") { + $dashbs = 1; + $error_mode = "s"; + print "220 premail ready to accept message, whoever you are\n"; + } elsif ($1 ne "m") { + exec (&bin_sendmail (), @_); + } + } elsif (/^\-[im]$/) { # ignore - from SunOS Mail + } elsif (/^\-decode$/) { + &decode (@_); + } elsif (/^\-makenym$/) { + &makenym (@_); + } elsif (/^\-importnym$/) { + $importnym = 1; + &makenym (@_); + } elsif (/^\-exportnym$/) { + &exportnym (@_); + } elsif (/^\-characterize$/) { + &characterize (@_); + } elsif (/^\-login$/) { + &login (@_); + } elsif (/^\-logout$/) { + &logout (@_); + } elsif (/^\-setpass$/) { + &setpass (@_); + } elsif (/^\-ripemkey$/) { + &ripemkey (@_); + } elsif (/^\-gist$/) { + &gist (@_); + } elsif (/^\+([\w\-]+)\=(.*)$/) { $cmdline_configs{$1} = $2; } + elsif ($post && /^([^\-].*)$/) { + if ($editfile eq '') { $editfile = $_; } + else { &error ("premail post: only one message at a time!\n"); } + } elsif (/^([^\-].*)$/) { push (@cmdline_recips, $_); } + else { &error ("unknown option $_ . Please send mail to" + ." raph\@c2\.org with details\n"); } + } + + if (!$dasht && !$dashbs && !$edit && !$post && $#cmdline_recips < 0) { + &error ("No recipients specified\n"); + } +} + +sub set_configs { + my ($preferences, $addresses, $recip); + + &apply_cmdline_configs (); + if ($config{'preferences'}) { + $preferences = &tilde_expand ($config{'preferences'}); + + if (open (PREF, $preferences)) { + while () { + if (/^\s*\$config\{\"([^\"]+)\"\}\s*\=\s*\"([^\"]*)\"/ + || /^\s*\$config\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { + $config{$1} = $2; + } + } + close (PREF); + } + } + &apply_cmdline_configs (); + if ($config{'addresses'}) { + + # 7-3-98 BAF -- Check file was opened before using it + if (open (ADDR, &tilde_expand ($config{'addresses'}))) { + while () { + if (/^([\w\-\_\+\.\@\!]+)\:\s*(.*)$/) { + $recip = &strip_address ($1); + $alias{$recip} = $2; + } + } + close (ADDR); + } else { + warn "Note: Assuming you don't want to use an address file.\n"; + } + + } + if ($config{'logfile'}) { + # 7-3-98 BAF -- Check file was opened before using it + open (LOG, '>>'.&tilde_expand_mkdir ($config{'logfile'})) || + die "Error: Can't open logfile for writing: $!"; + } + foreach (keys %config) { + &pdv ("\$config\{\'$_\'\} = \'$config{$_}\'\;\n"); + } +# foreach (keys %alias) { +# print "\$alias\{\'$_\'\} = \'$alias{$_}\'\;\n"; +# } +} + +sub apply_cmdline_configs { +# Apply the command line configs (as determined by parse_command_line) +# to the global configs. + foreach $entry (keys %cmdline_configs) { + $config{$entry} = $cmdline_configs{$entry}; + } +} + +sub open_input { +# $nonempty = &open_input () +# Open the input mail stream. If smtp mode, place recipient in +# cmdline_recips. + + $header_sep = ''; + $in_body = '-'; + if ($edit || $post) { + if (!open (IN, $editfile)) { + &error ("cannot open edit file $editfile\n"); + } + $in_active = 1; + return 1; + } elsif ($dashbs) { + # do simple SMTP + $_ = ; + if ($_ =~ /^quit/i) { + print "221 premail closing connection\n"; + return 0; + } + if ($_ =~ /^helo\s(.+)$/i) { + print "250 Hello $1, or whoever you really are\n"; + $_ = ; + } + if ($_ =~ /^mail from\:\s*(.*)$/i) { + print "250 Sender ok\n"; + $_ = ; + } + while ($_ =~ /^rcpt to\:\s*(.*)$/i) { + push (@cmdline_recips, $1); + print "250 Recipient ok\n"; + $_ = ; + } + if ($_ =~ /^data/i) { + print "354 Enter mail, end with \".\" on a line by itself\n"; + return 1; + } else { + print "521 Unknown error, closing connection\n"; + exit 1; + } + } else { + # input message on stdin, normal mode + return 1; + } +} + +sub get_header { +# &get_header ($body, $handle_from, $lax); +# Get the header from the input mail stream, store in @in_headers. Also, +# store the header separator line in $header_sep. +# +# If a second optional argument is given, handle a "From " line +# gracefully, returning it if present, or nothing if it's actually RFC +# 822. +# +# If a third optional argument is given, then be lax. Specifically, ignore +# an initial "From " (for elm forwarding) and don't require a blank line. + my ($body, $handle_from, $lax) = @_; + my ($line, $lineno); + + @in_headers = (); + for ($lineno = 0;;$lineno++) { + if (! defined($line = &get_line_body ($body))) { + last; + } + if ($handle_from && $lineno == 0 && $line =~ /^From /) { + return $line; + } + if ($line =~ /^([!-9\;-\177]+)\:\s*(.*)$/) { + push (@in_headers, $line); + } elsif ($#in_headers >= 0 && $line =~ /^\s(.*)\n/) { + $line = pop (@in_headers) . $line; + push (@in_headers, $line); + } elsif ($line eq '' || $line eq "\n" + || (($post || $edit) && $line eq "--------\n")) { + $header_sep = $line; + last; + } elsif ($lax && $lineno == 0 && $line =~ /^From /) { + } elsif ($lax) { + $header_sep = "\n"; + $pushline{$body} = $line; + last; + } else { + &error ("premail: bad header line:\n$line"); + } + } + if ($config{'debug'} =~ /h/) { &pdebug (@in_headers); } + return; +} + +sub get_line { +# $line = &get_line () +# Get a line from the input mail stream. Return undef on EOF. + my $line; + + if ($edit || $post) { + $line = if ($in_active); + } elsif ($dashbs) { + $line = ; + if (!defined $line || $line eq ".\n") { return undef; } + $line =~ s/^\.\./\./; + } else { + $line = ; + if (!defined $line || !$dashoi && $line eq ".\n") { return undef; } + } + $line =~ s/\r$// if defined $line; + return $line; +} + +sub close_input { +# Close input mail stream + +# if ($in_body ne '-') { +# &delete_tmpfile ($in_body); +# } + if ($edit || $post) { + close (IN); + $in_active = 0; + } elsif ($dashbs) { + print "250 Message accepted for delivery\n"; + $more_input = 1; + } +} + +sub prepare_for_n_passes { +# $new_body = &prepare_for_n_passes ($body, $n) +# Prepare for multiple passes over input body + my ($body, $n) = @_; + my ($new_body, $line); + + if ($body eq '-' && $n > 1) { + $new_body = &tmp_filename (); + open (TMP, '>'.$new_body) || + die "Error: Cannot create temporary files: $!"; + &open_body ($body); + while ($line = &get_line_body ($body)) { + print TMP $line; + } + &close_body ($body); + if ($body eq $in_body) { + $in_body = $new_body; + } + close (TMP); + } else { + $new_body = $body; + } + &refcnt_bump ($new_body, $n - 1); + return $new_body; +} + +sub open_body { +# &open_body ($in_body) +# Open a pass through the message body. + my ($body) = @_; + + if ($body ne '-') { + open (BODY, $body); + } +} + +sub get_line_body { +# $line = &get_body_line ($in_body) +# Get a line from the message body. Return undef on EOF. + my ($body) = @_; + my ($line); + + if (defined $pushline{$body}) { + return delete $pushline{$body}; + } elsif ($body ne '-') { + $line = ; # Need to store in scalar to avoid Perl 5.000 bug + return $line; + } else { + return &get_line (); + } +} + +sub close_body { +# &close_body ($in_body) +# Close a pass through the message body. + my ($body) = @_; + + if ($body ne '-') { + close (BODY); + &refcnt_bump ($body, -1); + } +} + +sub find_recips { +# Find all the recipients (from command line & header) and store in @recips. +# Also, set the value of $resent. + my ($key, $val); + + $resent = 0; + foreach (@in_headers) { + ($key, $val) = &parse_field ($_); + if ($key =~ # source: sendmail 8.6.8 conf.c + /^resent\-(sender|from|reply\-to|to|cc|bcc|message\-id|date)$/i) { + $resent = 1; + } + } + + # suppress cmdline remailers in -t mode; sendmail 8.6.8 manpage '-t' + if ($dasht) { + foreach (@cmdline_recips) { +# print ":".&strip_address($_).":\n"; + $ealias{&strip_address($_)} = ''; + } + } + + @recips = (); + if (!$dasht && !$edit && !$post || $dashbs) { + @recips = &expand_alias (@cmdline_recips); + } else { + foreach (@in_headers) { + ($key, $val) = &parse_field ($_); +# print "key = $key, val = $val\n"; + if ($resent && $key =~ /^resent\-(to|cc|bcc)$/i + || !$resent && $key =~ /^(to|cc|bcc)$/i) { + # follows sendmail 8.6.8 conf.c except for 'apparently-to' +# print &format_header ("split", &split_commas ($val)); + push (@recips, &expand_alias (&split_commas ($val))); + } + } + } + + if ($#recips < 0) { + &error ("No recipients specified, not even in the header\n"); + } +} + +sub prepare_send_header { +# Prepare @send_headers from @in_headers. Expands aliases and removes +# caret commands. Removes premail-specific headers, placing them into +# %header_premail_com. The @send_headers are not final, in that they may +# be twiddled with more, but at least they represent a common denominator +# among the groups. Places "Anon-X" headers in @anon_headers. +# +# Also computes the %which_header map, which tells which header each +# recipient "came from." This map is used to compute the "bcc" groups +# later. +# +# A note: this function doesn't care whether the -t option was used. The +# theory is that, even if -t is used, the headers probably match the +# command line anyway, so it is good to keep premail garbage from the +# recipients. This assumption is valid for the only -t mailer I know, +# which is elm. The worst that could possibly happen is that an alias +# gets wrongly expanded. +# +# Another note: this function will reformat the recipient lines nicely, +# according to the format_header rules. If you don't like it, tough. I +# did want to mention it, though, because it's the only way that premail +# will change the message if no premail options are specified. + my ($key, $val); + my (@my_recips, @expanded); + + @anon_headers = (); + @send_headers = (); + %header_premail_com = (); + foreach (@in_headers) { + ($key, $val) = &parse_field ($_); + if ($resent && $key =~ /^resent\-(to|cc|bcc)$/i + || !$resent && $key =~ /^(to|cc|bcc)$/i) { + # follows sendmail 8.6.8 conf.c except for 'apparently-to' + # why bother rewriting bcc's? just in case... + @my_recips = (); +# print &format_header ("Val", $val); + foreach (&split_commas ($val)) { +# print &format_header ("Stripped", &strip_address ($_)); + @expanded = &split_commas ($ealias{&strip_address ($_)}); +# print &format_header ("Expanded", @expanded); + if ($#expanded >= 0) { + foreach (@expanded) { + ($nocaret, $caret) = &strip_caret ($_); + $stripped = &strip_address ($nocaret); +# print "\$which_header\{'$stripped'} \= '$key'\;\n"; + $which_header{&strip_address ($nocaret)} = $key; + push (@my_recips, $nocaret); + } + } else { + ($nocaret, $caret) = &strip_caret ($_); + @my_recips = ($nocaret); + } + } + push (@send_headers, &format_header ($key, @my_recips)); + } elsif ($key =~ /^(key|encrypt\-(to|key))$/i) { + $header_premail_com{'encrypt-key'} = $val; + } elsif ($key =~ /^(mkey|encrypt\-mkey)$/i) { + $header_premail_com{'encrypt-mkey'} = $val; +# } elsif ($key =~ /^(skey|encrypt\-skey)$/i) { +# $header_premail_com{'encrypt-skey'} = $val; + } elsif ($key =~ /^(path|chain)$/i) { + $header_premail_com{'chain'} = $val; + } elsif ($key =~ /^sign$/i) { + $header_premail_com{'sign'} = $val; + } elsif ($key =~ /^msign$/i) { + $header_premail_com{'msign'} = $val; + } elsif ($key =~ /^ssign$/i) { + $header_premail_com{'ssign'} = $val; + } elsif ($key =~ /^no\-reply$/i) { + $header_premail_com{'no-reply'} = $val; + } elsif ($key =~ /^anon\-/i) { + s/^anon\-//i; + push (@anon_headers, $_); + } else { + push (@send_headers, $_); + } + } +} + +sub compute_groups { +# Assign each recipient to a group, storing the results in %recip_group +# (forward map), and %group_recips (inverse image). Store the list of +# groups in @groups. + my ($group); + + @groups = (); + %recip_group = (); + %group_recips = (); +# &pdv ("Group recips: ".join ('.', @recips)."\n"); + foreach $addr (@recips) { + $group = &group_of ($addr); + $recip_group{&strip_address ($addr)} = $group; + if (defined $group_recips{$group}) { + $group_recips{$group} .= ','.$addr; + } else { + push (@groups, $group); + $group_recips{$group} = $addr; + } + } +# print &format_header ("Groups", @groups); +} + +sub group_of { +# $group = &group_of ($full_addr) +# The rule is this: if two recipients are assigned the same group, then +# they can be sent with the same sendmail process. Within that constraint, +# try to make groups as large as possible. +# +# This might need a bit more work to support newsgroups as recipients. + my ($addr) = @_; + my ($key_type, $key, $sign_type, $sign, $chain_type, $chain); + my ($group, $strip); + my ($id_recip); + + ($key_type, $key) = &key_of ($addr); + ($chain_type, $chain, $sign_type, $sign) = &sender_info ($addr); + $group = 'norm'; + $strip = &strip_address ($addr); + $id_recip = 0; + if ($key_type ne '' && $which_header{$strip} =~ /bcc$/i) { + $group = 'bcc'; + $id_recip = 1; + } + if ($key_type ne '') { + $group .= '^'.$key_type; + } + if ($sign_type ne '') { + $group .= '^'.$sign_type.'='.$sign; + } + if ($chain_type ne '') { + if ($chain_type eq 'newnym') { + $group .= "^newnym.$chain"; + } + else { + $group .= '^chain'; + $id_recip = 1; + } + } + if ($id_recip) { + $group .= '^to='.$strip; + } + return $group; +} + +sub chain_info { +# ($chain_type, $nsign_type, $nsign) = &chain_info ($chain) + my ($chain) = @_; + return '' unless $chain; + if ($chain =~ /(.*;)?([\w-]+)=\s*([^\s\;\^]+)\s*$/) { + my ($remailer, $nymid) = ($2, "$2=$3"); + &get_remailers (); + if ($options{$remailer} =~ /\bnewnym\b/) { + &load_secrets (); + my $nym = &find_nym ($nymid); + if ($nym{$nym} =~ /(\^|^)signsend\=([^\^]*)(\^|\Z)/) { + return ('newnym', 'ring', $nymid) + if $2 eq 'p'; + return ('newnym', 'header', 'Nym-Commands: +signsend') + if $2 eq 'r'; + return ('newnym', 'error', "Nym $nymid not configured for " + . "signing."); + } + } + } + return ('chain', undef, undef); +} + +sub sender_info { +# ($chain_type, $chain, $sign_type, $sign) = &sender_info ($addr); +# +# Chain_of and sign_of are merged here, for some nyms have PGP keys. +# + my ($addr) = @_; + my ($strip, $caret) = &strip_caret ($addr); + my ($chain_type, $chain, $sign_type, $sign); + my ($nsign_type, $nsign); + + if ($caret =~ /\^chain\s*(\=([^\^]*))?(\^|$)/) { + $chain = $2 ? $2 : ''; + } elsif (defined $header_premail_com{'chain'}) { + $chain = $header_premail_com{'chain'}; + } elsif (defined $config{'defaultpath'}) { + $chain = $config{'defaultpath'}; + } + if (defined $chain) { + $chain =~ s/^\s+//; + $chain =~ s/\s+$//; + $chain = '3' unless $chain; + $chain = '' if $chain eq ';'; + ($chain_type, $nsign_type, $nsign) = &chain_info ($chain); + } + else { + $chain = $chain_type = ''; + } + + $sign_type = $sign = ''; + if ($caret =~ /\^(\w?sign)\s*(\=\s*([^\^]*?)\s*)?(\^|$)/) { + $sign_type = $1; + $sign = $3; + if (!defined $sign) { + if ($sign_type eq 'msign') { + $sign = 'me'; + } elsif ($sign_type eq 'sign' && $nsign_type) { + $sign_type = $nsign_type; + $sign = $nsign; + } elsif ($sign_type eq 'ssign' && defined $ripemuser) { + $sign = $ripemuser; + } elsif (defined $config{'signuser'}) { + $sign = $config{'signuser'}; + } else { + $sign = ''; + } + } + } elsif (defined $header_premail_com{'sign'}) { + $sign_type = 'sign'; + $sign = $header_premail_com{'sign'}; + if ($nsign_type && $sign !~ /\S/) { + $sign_type = $nsign_type; + $sign = $nsign; + } + } elsif (defined $header_premail_com{'msign'}) { + $sign_type = 'msign'; + $sign = $header_premail_com{'msign'}; + } elsif (defined $header_premail_com{'ssign'}) { + $sign_type = 'ssign'; + $sign = $header_premail_com{'ssign'}; + } + + return ($chain_type, $chain, $sign_type, $sign); +} + +sub chain_of { +# ($chain_type, $chain) = &chain_of ($full_addr) +# $chain_type will be one of {'', 'chain'} + return (&sender_info)[0,1]; +} + +sub sign_of { +# ($sign_type, $sign) = &sign_of ($full_addr) +# $sign_type will be one of {'', 'sign', msign', 'ssign'} + return (&sender_info)[2,3]; +} + +sub key_of { +# ($key_type, $key) = &key_of ($full_addr) +# $key_type will be one of {'', 'key', 'mkey', 'encrypt{,-des,-rc2}'} + my ($addr) = @_; + my ($strip, $caret, $key_type, $key); + + $key_type = ''; + $key = ''; + ($strip, $caret) = &strip_caret ($addr); + if ($caret =~ /\^(\w?key|encrypt|encrypt\-\w+)\s*(\=[^\^]*)?(\^|$)/) { + $key_type = $1; + if ($key_type eq 'encrypt-pgp') { $key_type = 'key'; } + $key = $2; + if (!defined $key) { + $key = &strip_address ($strip, 1); + } else { + $key =~ s/^\=\s*//; + } + if ($key eq '') { $key_type = ''; } + } elsif (defined $header_premail_com{'encrypt-key'}) { + $key_type = 'key'; + $key = $header_premail_com{'encrypt-key'}; + } elsif (defined $header_premail_com{'encrypt-mkey'}) { + $key_type = 'mkey'; + $key = $header_premail_com{'encrypt-mkey'}; + } elsif (defined $header_premail_com{'encrypt-skey'}) { + $key_type = 'skey'; + $key = $header_premail_com{'encrypt-skey'}; + } + return ($key_type, $key); +} + +sub send_group { +# &send_group ($group) +# Send the message in (@send_headers, $header_sep, $in_body) to all +# recipients in the group. + my ($group) = @_; + my (@the_recips); + my ($key_type, $key, $sign_type, $sign, $chain_type, $chain, $body); + my ($log, $subj, $subj_present); + +# print "\n"; +# print @send_headers; +# print $header_sep; +# &open_body ($in_body); +# while (defined ($_ = &get_line_body ($in_body))) { +# print; +# } +# &close_body ($in_body); + + @the_recips = &split_commas ($group_recips{$group}); +# &pdv ("the_recips".join (', ', @the_recips)."\n"); + &pdv (&format_header ("Recipients", @the_recips)); + @deliver_headers = @send_headers; + $body = $in_body; + + ($key_type, $key) = &key_of ($the_recips[0]); + ($chain_type, $chain, $sign_type, $sign) = &sender_info ($the_recips[0]); + if ($chain_type) { + &sanitize_deliver_headers (); + } + if ($sign_type || $key_type eq 'mkey' || $key_type =~ /^encrypt/) { + $body = &purify_mime ($body, 'sign'); + } elsif ($config{'purify-mime'}) { + $body = &purify_mime ($body, ''); + } + if ($key_type || $sign_type) { + $body = &transform_crypt ($body, @the_recips); + } + if ($chain_type) { + &get_remailers (); + $chain = &choose_chain ($chain); + if ($config{'debug'} =~ /r/) { + &pdebug ("Chose chain $chain\n"); + } + &pdv ("$chain_type $chain\n"); + &deliver_chain ($body, '', $chain, @the_recips); + } else { + &deliver ($body, '', @the_recips); + } + if ($config{'debug'} =~ /l/) { + $log = '!Sent '.join (', ', @the_recips); + if ($chain_type) { $log .= '['.$chain.']'; } + ($subj, $subj_present) = &lookup_val ('subject', @send_headers); + if ($subj_present) { $log .= ': '. $subj; } + print LOG ($log."\n"); + print LOG (&time (gmtime (time))."\n"); + } +} + +sub transform_crypt { +# $new_body = &transform_crypt ($body, @the_recips) +# Transform the messge in (@deliver_headers, $body) according to the +# key and sign parameters of the recipients. +# +# This function just does the dispatch to the individual crypt +# transformations. For now, there is just PGP and MOSS. Hopefully, +# S/MIME and, maybe, perl/RSA will follow shortly. + my ($body, @the_recips) = @_; + my ($key_type, $key, $sign_type, $sign); + + ($key_type, $key) = &key_of ($the_recips[0]); + ($sign_type, $sign) = &sign_of ($the_recips[0]); + if ($sign_type eq 'error') { + &error ($sign); + } + if ($sign_type eq 'header') { + push @deliver_headers, "$sign\n"; + return $body unless $key_type; + $sign_type = $sign = ''; + } + if ($key_type eq 'mkey' || $sign_type eq 'msign') { + if ($sign_type eq 'msign') { + $body = &transform_moss_sign ($body, @the_recips); + } + if ($key_type eq 'mkey') { + $body = &transform_moss_encrypt ($body, @the_recips); + } + return $body; + } elsif ($key_type =~ /^encrypt/ || $sign_type eq 'ssign') { + if ($sign_type eq 'ssign') { + $body = &transform_ripem_sign ($body, @the_recips); + } + if ($key_type =~ /^encrypt/) { + $body = &transform_ripem_encrypt ($body, @the_recips); + } + return $body; + } elsif ($key_type eq 'key') { + return &transform_pgp_encrypt ($body, @the_recips); + } elsif ($key_type eq '') { + if ($sign_type eq 'sign' || $sign_type eq 'ring') { + return &transform_pgp_sign ($body, @the_recips); + } else { + &error ("Unknown sign type: $sign_type\n"); + } + } else { + &error ("Unknown key type: $key_type\n"); + } +} + +sub transform_pgp_encrypt { +# $new_body = &transform_pgp_encrypt ($body, @the_recips) +# Transform the messge in (@deliver_headers, $body) according to the +# key and sign parameters of the recipients. In this case, that means +# PGP encryption and signing. + my ($body, @the_recips) = @_; + my ($key_type, $key); + my (@keys); + my ($new_body, $err, $line); + my (@mime_fields, $pgpmime, $prefix, $boundary); + my ($sign_type, $sign); + + @keys = (); + ($sign_type, $sign) = &sign_of ($the_recips[0]); + foreach $recip (@the_recips) { + ($key_type, $key) = &key_of ($recip); + push (@keys, $key); + } + $prefix = ''; + $pgpmime = 0; + (@mime_fields) = &extract_mime_fields (); + $pgpmime = ($config{'pgpmime'} || $#mime_fields >= 0); + if ($pgpmime) { + $prefix = join ('', @mime_fields)."\n"; + } + ($new_body, $err) = &pgp_encrypt($body, $prefix, $sign_type, $sign, '', + @keys); + if ($pgpmime) { + $boundary = '+'; + push (@deliver_headers, + 'MIME-Version: 1.0'."\n", + 'Content-Type: multipart/encrypted; boundary="'.$boundary.'";' + ."\n ".'protocol="application/pgp-encrypted"'."\n"); + $body = $new_body; + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; + print NEW "This message is in PGP/MIME format, according to the" + ." Internet Draft\n"; + print NEW "draft-elkins-pem-pgp-04.txt. For more information, see:\n"; + print NEW "http://www.c2.net/~raph/pgpmime.html\n"; + print NEW "\n"; + print NEW "--$boundary\n"; + print NEW "Content-Type: application/pgp-encrypted\n"; + print NEW "\n"; + print NEW "Version: 1\n"; + print NEW "\n"; + print NEW "--$boundary\n"; + print NEW "Content-Type: application/octet-stream\n"; + print NEW "\n"; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print NEW $line; + } + &close_body ($body); + print NEW "\n"; + print NEW "--$boundary--\n"; + close (NEW); + } + return $new_body; +} + +sub transform_pgp_sign { +# $new_body = &transform_pgp_sign ($body, @the_recips) +# Transform the messge in (@deliver_headers, $body) according to the +# sign parameter of the recipients. In this case, that means PGP signing. + my ($body, @the_recips) = @_; + my ($new_body, $err, $line); + my (@mime_fields, $pgpmime, $prefix, $boundary); + my ($sign_type, $sign); + + ($sign_type, $sign) = &sign_of ($the_recips[0]); + $prefix = ''; + $pgpmime = 0; + (@mime_fields) = &extract_mime_fields (); + $pgpmime = ($config{'pgpmime'} || $#mime_fields >= 0); + if (!$pgpmime) { + ($new_body, $err) = &pgp_clearsign ($body, $prefix, $sign, $sign_type); + } else { + $prefix = join ('', @mime_fields)."\n"; + ($new_body, $err, $boundary) + = &pgp_mime_sign ($body, $prefix, $sign, $sign_type); + push (@deliver_headers, + 'MIME-Version: 1.0'."\n", + 'Content-Type: multipart/signed; boundary="'.$boundary.'";' + ."\n ".'protocol="application/pgp-signature"; micalg=pgp-md5' + ."\n"); + } + return $new_body; +} + +sub extract_mime_fields { +# (@mime_fields) = &extract_mime_fields (); +# Extract the MIME fields from @deliver_headers, returning them. + my (@mime_fields); + my ($key); + + @mime_fields = &get_mime_fields (@deliver_headers); + foreach $key ('mime-version', 'content-type', + 'content-transfer-encoding', 'content-length', + 'content-md5') { + @deliver_headers = &delete_field ($key, @deliver_headers); + } + return (@mime_fields); +} + +sub transform_moss_encrypt { +# $new_body = &transform_moss_encrypt ($body, @the_recips) +# Transform the messge in (@deliver_headers, $body) according to the +# mkey parameter of the recipients. In this case, that means MOSS +# encryption. + my ($body, @the_recips) = @_; + my ($key_type, $key); + my ($new_body, $enc_body, $hdr_body, $errfile, $err, $line); + my (@mime_fields, $prefix, $boundary); + my ($invoc); + + (@mime_fields) = &extract_mime_fields (); + $prefix = join ('', @mime_fields)."\n"; + $invoc = &mossbin ('encrypt'); + foreach $recip (@the_recips) { + ($key_type, $key) = &key_of ($recip); + $invoc .= ' alias '.&shell_quote ($key); + } + $enc_body = &tmp_filename (); + $invoc .= ' data-out '.$enc_body; + $hdr_body = &tmp_filename (); + $invoc .= ' header-out '.$hdr_body; + $errfile = &tmp_filename (); + $invoc .= ' > '.$errfile.' 2>&1'; + if (!open (MOSS, "|$invoc")) { + &error ("Error invoking MOSS\n"); + } + print MOSS $prefix; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print MOSS $line; + } + close (MOSS); + $status = $?; + $err = &read_and_delete ($errfile); + if ($status) { &error ("MOSS error\n$err"); } + $boundary = '+'; + push (@deliver_headers, + 'MIME-Version: 1.0'."\n", + 'Content-Type: multipart/encrypted; boundary="'.$boundary.'";' + ."\n ".'protocol="application/moss-keys"'."\n"); + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; + print NEW "--$boundary\n"; + print NEW "Content-Type: application/moss-keys\n"; + print NEW "Content-Transfer-Encoding: quoted-printable\n"; + print NEW "\n"; + &open_body ($hdr_body); + while (defined ($line = &get_line_body ($hdr_body))) { + print NEW &encode_qp ($line, 'sign'); + } + &close_body ($hdr_body); + print NEW "\n"; + print NEW "--$boundary\n"; + print NEW "Content-Type: application/octet-stream\n"; + print NEW "Content-Transfer-Encoding: base64\n"; + print NEW "\n"; + open (B64, &mossbin('mossencode').' -b64 < '.$enc_body.' |'); + &open_body ($enc_body); + while (defined ($line = )) { + print NEW $line; + } + close (B64); + &delete_tmpfile ($enc_body); + print NEW "\n"; + print NEW "--$boundary--\n"; + close (NEW); + return $new_body; +} + +sub transform_moss_sign { +# $new_body = &transform_moss_sign ($body, @the_recips) +# Transform the messge in (@deliver_headers, $body) according to the +# msign parameter of the recipients. In this case, that means MOSS +# signing. + my ($body, @the_recips) = @_; + my ($key_type, $key); + my ($new_body, $hdr_body, $errfile, $err, $line); + my (@mime_fields, $prefix, $boundary); + my ($invoc); + my ($sign_type, $sign); + + ($sign_type, $sign) = &sign_of ($the_recips[0]); + $prefix = ''; + (@mime_fields) = &extract_mime_fields (); + $prefix = join ('', @mime_fields)."\n"; + $invoc = &mossbin ('sign'); + $invoc .= ' sig-alias '.&shell_quote ($sign); + $hdr_body = &tmp_filename (); + $invoc .= ' header-out '.$hdr_body; + $errfile = &tmp_filename (); + $invoc .= ' > '.$errfile.' 2>&1'; + open (MOSS, "|$invoc"); + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; + $boundary = &random (80); + push (@deliver_headers, + 'MIME-Version: 1.0'."\n", + 'Content-Type: multipart/signed;' + .' protocol="application/moss-signature";' + ."\n ".'micalg=rsa-md5; boundary="'.$boundary.'"'."\n"); + print NEW "--$boundary\n"; + print NEW $prefix; + print MOSS &canonicalize_line_moss ($prefix); + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print NEW $line; + print MOSS &canonicalize_line_moss ($line); + } + close (MOSS); + $status = $?; + $err = &read_and_delete ($errfile); + if ($status) { &error ("MOSS error\n$err"); } + print NEW "\n"; + print NEW "--$boundary\n"; + print NEW "Content-Type: application/moss-signature\n"; + print NEW "Content-Transfer-Encoding: quoted-printable\n"; + print NEW "\n"; + &open_body ($hdr_body); + while (defined ($line = &get_line_body ($hdr_body))) { + print NEW &encode_qp ($line, 'sign'); + } + &close_body ($hdr_body); + print NEW "\n"; + print NEW "--$boundary--\n"; + close (NEW); + return $new_body; +} + +sub mossbin { +# $full_path = &mossbin ($progname) +# Return the full path of a MOSS program, given the program's name. +# Generate an error if the program is not executable. +# +# If optional second argument is given, then fail more softly. + my ($progname, $fail_soft) = @_; + my ($dir, $fn); + + $dir = $config{'mossbin'}; + if ($dir =~ /[^\/]$/) { $dir .= '/'; } + $fn = $dir.$progname; + if (! -x $fn) { + if ($fail_soft) { return ''; } + &error ("Cannot find MOSS program $progname (full path $fn)\n"); + } + return $fn; +} + +sub transform_ripem_sign { +# This routine does the multipart/signed message format. + my ($body, @the_recips) = @_; + my ($key_type, $key); + my (@keys); + my ($err, $line); + my (@mime_fields, $prefix, $boundary); + my ($sign_type, $sign); + my ($invoc, $errfile); + my ($in_body, $sig_body); + my ($user); + + @keys = (); + ($sign_type, $sign) = &sign_of ($the_recips[0]); + foreach $recip (@the_recips) { + ($key_type, $key) = &key_of ($recip); + if ($key_type eq 'skey') { push (@keys, $key); } + } + &load_secrets (); + if ($sign_type eq 'ssign' && $sign ne '') { + $user = $sign; + } elsif (defined $ripemuser) { + $user = $ripemuser; + } else { + &error ("Must specify \$ripempass{''} = ''; in secrets file\n"); + } + if (!defined $ripempass{$user}) { + &error ("Must specify \$ripempass{'$user'} = ''; in secrets file\n"); + } + (@mime_fields) = &extract_mime_fields (); + $prefix = join ('', @mime_fields)."\n"; + # Here's where we actually invoke ripem + $invoc = &tilde_expand ($config{'ripem'}); + $invoc .= ' -e -M pkcs -k - -u '.$user; + $invoc .= ' -m mic-only'; + $in_body = &canonicalize_body ($prefix, $body); + $invoc .= ' -x '.$in_body; + $sig_body = &tmp_filename (); + $invoc .= ' -o '.$sig_body; + $errfile = &tmp_filename (); + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking RIPEM as $invoc\n"); + if (!open (RIPEM, "|$invoc")) { + &error ("Error invoking RIPEM\n"); + } + print RIPEM ($ripempass{$user}."\n"); + close (RIPEM); + $status = $?; + $err = &read_and_delete ($errfile); + if ($status) { &error ("RIPEM error\n$err"); } + &pdv ($err); + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; + $boundary = &random (80); + push (@deliver_headers, + 'MIME-Version: 1.0'."\n", + 'Content-Type: multipart/signed;' + .' protocol="application/x-pkcs7-signature";' + ."\n ".'micalg=rsa-md5; boundary="'.$boundary.'"'."\n"); + print NEW "--$boundary\n"; + &open_body ($in_body); + while (defined ($line = &get_line_body ($in_body))) { + print NEW $line; + } + &close_body ($in_body); + print NEW "\n"; + print NEW "--$boundary\n"; + print NEW ('Content-Type: application/x-pkcs7-signature'."\n"); + print NEW ('Content-Transfer-Encoding: base64'."\n"); + print NEW "\n"; + &open_body ($sig_body); + while (defined ($line = &get_line_body ($sig_body))) { + print NEW $line; + } + &close_body ($sig_body); + print NEW "\n"; + print NEW "--$boundary--\n"; + close (NEW); + return $new_body; +} + +sub transform_ripem_encrypt { +# $new_body = &transform_ripem_encrypt ($body, @the_recips) +# Transform the messge in (@deliver_headers, $body) according to the +# key and sign parameters of the recipients. In this case, that means +# S/MIME encryption and/or signing using RIPEM. +# +# Actually, RIPEM 3.0 can't do encrypt-only - it always needs to sign. + my ($body, @the_recips) = @_; + my ($key_type, $key); + my (@keys); + my ($err, $line); + my (@mime_fields, $prefix); + my ($sign_type, $sign); + my ($invoc, $errfile); + my ($in_body); + my ($user); + + @keys = (); + # Enable the following to make this routine do PKCS signing +# ($sign_type, $sign) = &sign_of ($the_recips[0]); + foreach $recip (@the_recips) { + ($key_type, $key) = &key_of ($recip); + if ($key_type =~ /^encrypt/) { push (@keys, $key); } + } + &load_secrets (); + if ($sign_type eq 'ssign' && $sign ne '') { + $user = $sign; + } elsif (defined $ripemuser) { + $user = $ripemuser; + } else { + &error ("Must specify \$ripempass{''} = ''; in secrets file\n"); + } + if (!defined $ripempass{$user}) { + &error ("Must specify \$ripempass{'$user'} = ''; in secrets file\n"); + } + (@mime_fields) = &extract_mime_fields (); + $prefix = join ('', @mime_fields)."\n"; + # Here's where we actually invoke ripem + $invoc = &tilde_expand ($config{'ripem'}); + $invoc .= ' -e -M pkcs -k - -u '.$user; + if ($#keys < 0) { + $invoc .= ' -m mic-only'; + } else { + if ($sign_type ne 'ssign') { + $invoc .= ' -m enveloped-only'; + } + if ($key_type eq 'encrypt') { $invoc .= ' -A des-ede-cbc'; } + elsif ($key_type ne 'encrypt-des') { + &error ("Unsupported encryption algorithm $key_type\n"); + } + $invoc .= ' -Ta'; + foreach $k (@keys) { + $invoc .= ' -r '.&shell_quote ($k); + } + } + $in_body = &canonicalize_body ($prefix, $body); + $invoc .= ' -i '.$in_body; + $new_body = &tmp_filename (); + $invoc .= ' -o '.$new_body; + $errfile = &tmp_filename (); + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking RIPEM as $invoc\n"); + if (!open (RIPEM, "|$invoc")) { + &error ("Error invoking RIPEM\n"); + } + print RIPEM ($ripempass{$user}."\n"); + close (RIPEM); + $status = $?; + $err = &read_and_delete ($errfile); + if ($status) { &error ("RIPEM error\n$err"); } + &pdv ($err); + push (@deliver_headers, + 'MIME-Version: 1.0'."\n", + 'Content-Type: application/x-pkcs7-mime'."\n", + 'Content-Transfer-Encoding: base64'."\n"); + return $new_body; +} + +sub canonicalize_body { +# $new_body = &canonicalize_body ($prefix, $body) +# Force the body into a file, and canonicalize it. +# +# With RIPEM 3.0b1, must canonicalize to LF line ends. + my ($prefix, $body) = @_; + my ($new_body); + + $new_body = &tmp_filename (); + open (FORCE, '>'.$new_body) || + die "Couldn't open file: $!"; + print FORCE &canonicalize_line_enc ($prefix); + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print FORCE &canonicalize_line_enc ($line); + } + close (FORCE); + return ($new_body); +} + +sub force_file_body { +# $new_body = &force_file_body ($body) +# Force the body into a file. + my ($body) = @_; + my ($new_body); + + if ($body ne '-') { return $body; } + $new_body = &tmp_filename (); + open (FORCE, '>'.$new_body) || + die "Couldn't open file: $!"; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print FORCE $line; + } + close (FORCE); + return ($new_body); +} + +# Routines for dealing with anonymous remailer chains follow. + +sub sanitize_deliver_headers { +# &sanitize_deliver_headers () +# Remove any potentially identity-revealing information in the delivery +# headers. +# +# Not right yet. Empty for now. +} + +sub choose_chain { +# $chosen_chain = &choose_chain ($chain_spec, $erb) +# Choose a chain, filling in any random subchains specified by integers. +# If an optional second argument is given, then the chain will be +# optimized for encrypted reply blocks rather than one-time mail. +# Not right yet - still need to verify the keys of PGP mailers. + my ($chain, $erb) = @_; + my (@chain, $i); + my (@new_chain, $best, $best_mailer, $score); + my (@options, $numshuf); + my (@link_group); + my (%link); + + @chain = reverse (&split_chain ($chain)); # choose in reverse order + if ($config{"numshuf"}) { + $num_shuf = $config{"numshuf"}; + } else { + $num_shuf = 3; + } + if ($config{"reliability-threshold"}) { + $rel_thres = $config{"reliability-threshold"}; + } else { + $rel_thres = 100; + } + if ($config{"latency-threshold"}) { + $lat_thres = $config{"latency-threshold"}; + } else { + $lat_thres = 0; + } + foreach $hop (@chain) { + if ($hop =~ /^\d+$/) { + for ($i = 0; $i < $hop; $i++) { + $best = -1000; + $bestmailer = ''; + foreach $remailer (keys %reliability) { + @options = split (/ /, $options{$remailer}); + if (!(&member ('cpunk', @options) + || &member ('eric', @options))) { + next; + } + if ($reliability{$remailer}>=$rel_thres ) { + $score = 100; + } else { + $score = $reliability{$remailer}; + } + if ($latency{$remailer}>=$lat_thres) { + $score -= $latency{$remailer} * 1e-5; + } + if ($config{'encrypt'} && + (&member ('pgp', @options) + || &member ('pgp.', @options))) { + $score += 10; + if ($erb && &member ('ek', @options)) { + $score += 5; + } + } elsif ($config{'pgp-only'} + || &member ('pgponly', @options)) { next; } + if ($config{'no-middle'} + && &member ('middle', @options)) { next; } + if (&member ('reord', @options) + && $rel_thres==100) { $score += 0.1; } + if (&member ('filter', @options)) { $score -= 10; } + if (&member ('mon', @options)) { $score -= 10; } + if ($#new_chain < 0 && !$erb + && !(&member ('hash', @options) || + &member ('special', @options))) { + # Might look at header, only need to do this if + # either there are funky headers, or if the mailer + # is nsub. + next; + } + if (($#chain >= 1 || $hop > 1) + && &member ('?', @options)) { next; } + if ($link{$remailer}) { $score -= $link{$remailer}; } + $score += $num_shuf * rand () * 0.1; + if ($score > $best) { + $best = $score; + $bestmailer = $remailer; + } + } + if ($bestmailer eq '') { + &error ("Can't find remailers!\n"); + } + push (@new_chain, $bestmailer); + foreach (keys %link) { + $link{$_} *= 0.75; + } + $link{$bestmailer} = 100; + foreach $link_group (@links) { + @link_group = split (/ /, $link_group); + if (&member ($bestmailer, @link_group)) { + foreach $linked (@link_group) { + $link{$linked} += 1; + } + } + } +# foreach (keys %link) { +# print "$_ $link{$_}\n"; +# } +# print "\n"; + } + } else { + push (@new_chain, $hop); + } + } + return join (';', reverse (@new_chain)); +} + +sub split_chain { +# @split = &split_chain ($chain) +# Split a chain into hops. Each mixmaster subchain counts as one hop. +# Not right yet (need to handle mix subchains & strip whitespace). + my (@raw_chain, @chain, $mix); + + @raw_chain = split (/\s*\;\s*/, $_[0]); + @chain = (); + $mix = ''; + foreach (@raw_chain) { + if (/^\(/) { $mix = $_; } + elsif ($mix) { $mix .= ';'.$_; } + else { push (@chain, $_); } + if ($mix && /\)$/) { push (@chain, $mix); $mix = ''; } + } + return @chain; +} + +sub get_remailers { +# Get the remailer-list. For each remailer, store an entry into +# %address, %options, %latency (in seconds), %reliability (in +# percent), and @links. + my ($remailers_file, $state); + my ($remailer, $latency); + + if ($got_remailers) { return; } + $got_remailers = 1; + $remailers_file = &tilde_expand_mkdir ($config{'rlist'}); + if (&is_stale ($remailers_file, $config{'rlist-valid'}) + && $config{'rlist-url'}) { + &getfile_from_web_html ($remailers_file, $config{'rlist-url'}); + &getfile_from_web_html (&tilde_expand_mkdir ($config{'pubring'}), + $config{'pubring-url'}); + } + open (REMAILERS, $remailers_file) || + die "Configuration error: Can't open Remailers file: $!"; + while () { + if (/^\s*\$remailer\{\"([^\"]+)\"\}\s*\=\s*\"([^\"]*)\"/ + || /^\s*\$remailer\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { + $remailer = $1; + if ($2 =~ /\<([^\>]+)\>\s(.*)$/) { + $address{$remailer} = $1; + $options{$remailer} = $2; + } + } elsif (/^\((.*)\)$/) { + push (@links, $1); + } + if (/--------/) { + $state = 1; + } + if ($state && $_ eq "\n") { + $state = 0; + } + if ($state && + /^([\w\-]+).*[^\d\:](\d+\:\d+\:\d+|\d*\:\d+)\s+([\d\.]+)\%/) { + $remailer = $1; + $latency = $2; + $reliability{$remailer} = $3; + if ($latency =~ /^(\d+)\:(\d+)\:(\d+)$/) { + $latency = 3600 * $1 + 60 * $2 + $3; + } elsif ($latency =~ /^(\d+)\:(\d+)$/) { + $latency = 60 * $1 + $2; + } elsif ($latency =~ /^\:(\d+)$/) { + $latency = $1; + } + $latency{$remailer} = $latency; + } + } + close (REMAILERS); +} + +sub getfile_from_web { +# &getfile_from_web ($file, $url) +# Get the file from the url. + my ($file, $url) = @_; + + if (&open_web ($url)) { + open (PUT, '>'.$file) || + die "Couldn't open file: $!"; + while () { + print PUT; + } + close (WWW); + close (PUT); + } +} + +sub getfile_from_web_html { +# &getfile_from_html ($file, $url) +# Get the file from the url. +# +# Only actually update the file if it is five lines or more. +# +# If a
 tag is present within the first five lines, extract
+# information between 
 and 
tags, discarding the rest. + my ($file, $url) = @_; + my (@window, $yup, $inpre, $put_open); + +# print "getfile_from_web_html: $file, $url\n"; + $inpre = 0; + $yup = 0; + $put_open = 0; + if (&open_web ($url)) { + while () { + if (!$yup && !$inpre && /^\s*\\s*$/i) { + open (PUT, '>'.$file) || + die "Couldn't open file: $!"; + $put_open = 1; + $inpre = 1; + } elsif ($inpre && /^\s*\<\/pre\>\s*$/i) { + $inpre = 0; + } else { + if ($inpre) { + s/\<\;/\/g; + s/\&\;/\&/g; + } + if ($inpre || $yup) { + print PUT; + } else { + push (@window, $_); + if ($#window + 1 == 5) { + open (PUT, '>'.$file) || + die "Couldn't open file: $!"; + $put_open = 1; + print PUT @window; + $yup = 1; + } + } + } + } + if ($put_open) { close (PUT); } + close (WWW); + } +} + +sub get_mixmasters { +# Get the mixmaster information. Store in $mix_dir, $mix_type2_list, +# %mix_addr, and %mix_num. + my ($mix, $num); + + if ($got_mixmasters) { return; } + $got_mixmasters = 1; + $mix = &tilde_expand ($config{'mixmaster'}); + if (!open (MIX, "$mix -P|")) { + &error ("Cannot execute $mix\n"); + } + $mix_dir = ; + $mix_type2_list = ; + close (MIX); + if (!defined $mix_dir || $mix_dir eq '') { + &error ( + "Cannot get information from mixmaster - need version 2.0.2 or better\n"); + } + chop $mix_dir; + chop $mix_type2_list; + $type2_list = $mix_dir.'/'.$mix_type2_list; + if (!-e $type2_list) { + &error ("Cannot find type2.list; not at $type2_list\n"); + } + open (LIST, "$type2_list") || + die "Couldn't open file: $!"; + $num = 0; + while () { + if (/^(\S+)\s+(\S+)\s/) { + $num++; + $mix_num{$1} = $num; + $mix_addr{$1} = $2; + } + } + close (LIST); + if ($num == 0) { + &error ("No mixmasters in list $type2_list\n"); + } +} + +sub deliver_chain { +# &deliver_chain ($body, $prefix, $chain, @the_recips) +# Deliver the message composed of (@deliver_headers, $header_sep, $prefix, +# $body) to @the_recips (usually singular, but may be plural if we fiddle +# delivery to newsgroups), through chain $chain. +# +# This routine may mutate @deliver_headers. It is recursive so that each +# packet of a Mixmaster message may be delivered separately. + my ($body, $prefix, $chain, @the_recips) = @_; + my (@chain, $full_hop, $hop, $recip, $new_to); + + &pdv ("deliver_chain $chain ".join (',', @the_recips)."\n"); + @chain = &split_chain ($chain); + if ($#chain < 0) { + &deliver ($body, $prefix, @the_recips); + return; + } + # We know chain is at least one element - process last hop + $full_hop = pop (@chain); + $hop = $full_hop; + $hop =~ s/^([\w\-]+).*$/$1/; + $chain = join (';', @chain); + if ($hop =~ /^\(.*\)$/) { + &deliver_chain_mix ($body, $prefix, $chain, $hop, @the_recips); + return; + } + if (!defined $options{$hop}) { + &error ("Unknown remailer $hop\n"); + } + @options = split (/ /, $options{$hop}); + if (&member ('cpunk', @options) || &member ('eric', @options) + || &member ('penet', @options)) { + &deliver_chain_cpunk ($body, $prefix, $chain, $full_hop, @the_recips); + } elsif (&member ('newnym', @options)) { + &deliver_chain_newnym ($body, $prefix, $chain, $full_hop, @the_recips); + } elsif (&member ('alpha', @options)) { + &deliver_chain_alpha ($body, $prefix, $chain, $full_hop, @the_recips); + } else { + &error ("Don't know how to prepare messages for remailer $hop\n"); + } +} + +sub deliver_chain_cpunk { +# &deliver_chain ($body, $prefix, $chain, $hop, @the_recips) +# Deliver the message composed of (@deliver_headers, $header_sep, $prefix, +# $body) to @the_recips (usually singular, but may be plural if we fiddle +# delivery to newsgroups), through chain ($chain, $hop), where we know +# that the last hop is a cypherpunks variant remailer (cpunks, eric, +# penet). +# +# This thing is a bloody mess. + my ($body, $prefix, $chain, $hop, @the_recips) = @_; + my ($recip, $new_to, $hash, $encrypt, $key, $err, $req); + my ($subj, $subj_present); + my (@hash_headers); + my ($addl); + + if ($hop =~ /^([\w\-]*)(\..*)$/) { + $hop = $1; + $addl = $2; + } + @options = split (/ /, $options{$hop}); + $encrypt = ((&member ('pgp', @options) || &member ('pgp.', @options)) + && $config{'encrypt'}); + $recip = &strip_and_join (@the_recips); + $new_to = $address{$hop}; + ($subj, $subj_present) = &lookup_val ('subject', @deliver_headers); + $hash = ''; + if (&member ('hash', @options) || &member ('special', @options)) { + @hash_headers = &get_anon_headers (); + if (($encrypt || &member ('ksub', @options)) + && !&member ('eric', @options) && !&member ('nsub', @options)) { + if ($subj_present) { push (@hash_headers, "Subject: $subj\n"); } + } elsif (!&member ('eric', @options)) { + if ($subj_present) { push (@deliver_headers, "Subject: $subj\n"); } + } + $hash = join ('', @hash_headers); + if (!&member ('special', @options) && $#hash_headers >= 0) { + $hash = "\n\#\#\n".$hash; + } + } else { + @deliver_headers = (); + if ($subj_present && !&member ('eric', @options)) { + push (@deliver_headers, "Subject: $subj\n"); + } + } + push (@deliver_headers, "To\: $new_to\n"); + if (defined $addl && $addl =~ /\.(encrypt\-key\:\s*[^\.]+)(\.|$)/i) { + $hash = "$1\n".$hash; + $body = &cat_tail ($body, "\*\*\n"); + } + if (&member ('eric', @options)) { + $req = 'Anon-Send-To'; + if ($subj_present) { $hash = "Subject: $subj\n".$hash; } + } else { + $req = 'Request-Remailing-To'; + } + if (&member ('penet', @options)) { + push (@deliver_headers, 'X-Anon-To: '.$recip."\n"); + if ($chain eq '') { + &load_secrets (); + if (defined $penetpass) { + push (@deliver_headers, 'X-Anon-Password: '.$penetpass."\n"); + } + } + } else { + $prefix = '::'."\n" + .$req.': '.$recip."\n" + .$hash + ."\n" + .$prefix; + } + if ($encrypt) { + if (&member ('pgp', @options)) { + $key = $new_to; + } else { + $key = $hop; + } + ($body, $err) = &pgp_encrypt + ($body, $prefix, '', '', &tilde_expand ($config{'pubring'}), $key); + if (&member ('special', @options)) { + $prefix = ''; + } else { + $prefix = "\:\:\nEncrypted\: PGP\n\n"; + } + } elsif (&member ('special', @options)) { + &error ("Remailer $hop requires encryption\n"); + } + &deliver_chain ($body, $prefix, $chain, $new_to); +} + +sub cat_tail { +# Append $postfix at end of $body. Return new file. + my ($body, $postfix) = @_; + my ($outfile, $line); + + $outfile = &tmp_filename (); + open (OUT, '>'.$outfile) || + die "Couldn't open file: $!"; + open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print OUT $line; + } + &close_body ($body); + print OUT $postfix; + close (OUT); + return ($outfile); +} + +sub deliver_chain_alpha { +# &deliver_chain ($body, $prefix, $chain, $hop, @the_recips) +# Deliver the message composed of (@deliver_headers, $header_sep, $prefix, +# $body) to @the_recips (usually singular, but may be plural if we fiddle +# delivery to newsgroups), through chain ($chain, $hop), where we know +# that the last hop is an alpha remailer. +# +# Safe delivery of MIME messages has not been tested and probably doesn't +# work. + my ($body, $prefix, $chain, $full_hop, @the_recips) = @_; + my ($recip, $new_to, $hash, $key, $err, $req); + my ($subj, $subj_present); + my (@anon_headers); + my ($hop, $nym, $short_nym, $pass, $addrtail, $from); + + &load_secrets (); + ($subj, $subj_present) = &lookup_val ('subject', @deliver_headers); + @anon_headers = &get_anon_headers (); + if ($full_hop =~ /^([\w\-]*)\=(.*)$/) { + $hop = $1; + $short_nym = $2; + } else { + $hop = $full_hop; + ($val, $present) = &lookup_val ('from', @anon_headers); + if ($present) { + $nym = &strip_address ($val); + if ($nym =~ /^([^\@]+)\@(.*)$/) { + $short_nym = $1; + $full_hop = $hop.'='.$short_nym; + } else { + &error ("Need to specify full nym address in Anon-From:" + ." field\n"); + } + } else { + &error ("Alpha remailers require nym argument, in alpha=nym" + ." format\n"); + } + } + $nym = &find_nym ($full_hop); + if ($nym eq '') { + &error ("Nym $full_hop not found\n"); + } + @options = split (/ /, $options{$hop}); + if ($nym{$nym} =~ /(\^|^)pass\=([^\^]*)(\^|$ )/) { + $pass = $2; + } else { + &error ("Password not set for nym $full_hop\n"); + } + $recip = &strip_and_join (@the_recips); + $new_to = $address{$hop}; + @deliver_headers = ("To\: $new_to\n"); + $from = $short_nym.'@'.$address{$hop}; + ($val, $present) = &lookup_val ('from', @anon_headers); + if ($present) { + $from = $val; + @anon_headers = &delete_field ("from", @anon_headers); + } + $addrtail = $address{$hop}; + $addrtail =~ s/^([^\@]+)\@//; + $prefix = 'From: '.$from."\n"; + $prefix .= 'Password: '.$pass."\n"; + $prefix .= 'Subject: '.$subj."\n" if $subj_present; + $prefix .= 'Ack: no'."\n" unless $config{'ack'}; + $prefix .= 'To: '.$recip."\n"; + $prefix .= join ('', @anon_headers)."\n"; + if (&member ('pgp', @options)) { + $key = $new_to; + } else { + $key = $hop; + } + ($body, $err) = &pgp_encrypt + ($body, $prefix, '', '', &tilde_expand ($config{'pubring'}), $key); + $prefix = ''; + &deliver_chain ($body, $prefix, $chain, $new_to); +} + +sub deliver_chain_newnym { +# &deliver_chain ($body, $prefix, $chain, $hop, @the_recips) +# Deliver the message composed of (@deliver_headers, $header_sep, $prefix, +# $body) to @the_recips (usually singular, but may be plural if we fiddle +# delivery to newsgroups), through chain ($chain, $hop), where we know +# that the last hop is an alpha remailer. +# +# Safe delivery of MIME messages has not been tested and probably doesn't +# work. + my ($body, $prefix, $chain, $full_hop, @the_recips) = @_; + my ($recip, $new_to, $hash, $key, $err, $req); + my ($subj, $subj_present); + my (@anon_headers); + my ($hop, $nym, $short_nym, $addrtail, $from); + + &load_secrets (); + ($subj, $subj_present) = &lookup_val ('subject', @deliver_headers); + @anon_headers = &get_anon_headers (1, 1); + if ($full_hop =~ /^([\w\-]*)\=(.*)$/) { + $hop = $1; + $short_nym = $2; + } else { + $hop = $full_hop; + ($val, $present) = &lookup_val ('from', @anon_headers); + if ($present) { + $nym = &strip_address ($val); + if ($nym =~ /^([^\@]+)\@(.*)$/) { + $short_nym = $1; + $full_hop = $hop.'='.$short_nym; + } else { + &error ("Need to specify full nym address in Anon-From:" + ." field\n"); + } + } else { + &error ("Newnym remailers require nym argument, in nym=yournym" + ." format\n"); + } + } + $nym = &find_nym ($full_hop); + if ($nym eq '') { + &error ("Nym $full_hop not found\n"); + } + @options = split (/ /, $options{$hop}); + &error ("No RSA key for nym $full_hop\n") + unless ($pgpring{$full_hop}); + $recip = &strip_and_join (@the_recips); + $new_to = $address{$hop}; + $new_to =~ s/^config\@(.*)/send\@$1/; + @deliver_headers = ("To\: $new_to\n"); + $from = $address{$hop}; + $from =~ s/^[^\@]*/$short_nym/; + ($val, $present) = &lookup_val ('from', @anon_headers); + if ($present) { + $from = $val; + @anon_headers = &delete_field ("from", @anon_headers); + } + $addrtail = $address{$hop}; + $addrtail =~ s/^([^\@]+)\@//; + $prefix = 'From: '.$from."\n"; + $prefix .= 'Subject: '.$subj."\n" if $subj_present; + $prefix .= 'Hidden-To: '.$recip."\n"; + $prefix .= join ('', @anon_headers)."\n"; + if (&member ('pgp', @options)) { + $key = $new_to; + } else { + $key = $hop; + } + # The following invocation adds the remailer's key twice: once + # from the public key part of $pgpring{$full_hop}, and once from + # $key. That's ok, but not really necessary. + ($body, $err) = &pgp_encrypt ($body, $prefix, 'ring', $full_hop, + &tilde_expand ($config{'pubring'}), $key); + $prefix = ''; + &deliver_chain ($body, $prefix, $chain, $new_to); +} + +sub deliver_chain_mix { +# &deliver_chain ($body, $prefix, $chain, $hop, @the_recips) +# Deliver the message composed of (@deliver_headers, $header_sep, $prefix, +# $body) to @the_recips (usually singular, but may be plural if we fiddle +# delivery to newsgroups), through chain ($chain, $hop), where we know +# that the last hop is a Mixmaster subchain. + my ($body, $prefix, $chain, $hop, @the_recips) = @_; + my ($invoc, $mixfn, $line, $new_to, $i); + my (@hop); + my ($subj, $subj_present); + + &get_mix_keys (); + &get_mixmasters (); + ($subj, $subj_present) = &lookup_val ('subject', @deliver_headers); + $mixfn = &tmp_filename (); + $invoc = &tilde_expand ($config{'mixmaster'}).' -f -o '.$mixfn.' -l'; + $hop =~ s/\((.*)\)/$1/; + @hop = split (/;/, $hop); + foreach (@hop) { + if (!$mix_num{$_}) { + &error ("Mixmaster remailer $_ unknown\n"); + } + $invoc .= ' '.$mix_num{$_}; + } + $new_to = $mix_addr{$hop[0]}; + if (!open (MIX, "|".$invoc)) { + &error ("Error invoking mixmaster, command line is:\n$invoc\n"); + } + foreach (@the_recips) { + print MIX &strip_address ($_, 1)."\n"; + } + print MIX "\n"; + if ($subj_present) { &pdv ("Subject: $subj\n"); print MIX "Subject: $subj\n"; } + @deliver_headers = &get_anon_headers (); + foreach (@deliver_headers) { + print MIX; + } + print MIX "\n"; + print MIX $prefix; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print MIX $line; + } + &close_body ($body); + close MIX; + if ($?) { &error ("Mixmaster error\n"); } # should we capture stderr? + if (-e $mixfn) { + @deliver_headers = ("To: $new_to\n"); + &deliver_chain ($mixfn, '', $chain, $new_to); + } elsif (-e $mixfn.'.1') { + for ($i = 1; -e $mixfn.'.'.$i; $i++) { + push (@open_tmpfiles, $mixfn.'.'.$i); + $tmpfile_refcnt{$mixfn.'.'.$i} = 1; + @deliver_headers = ("To: $new_to\n"); + &deliver_chain ($mixfn.'.'.$i, '', $chain, $new_to); + } + } else { + &error ("Mixmaster did not generate any files to send\n"); + } +} + +sub get_anon_headers { +# @headers = &get_anon_headers ($keeprecip); +# Get all the headers to send anonymously, from @deliver_headers and +# @anon_headers. Kills both @deliver_headers and @anon_headers. +# Does not get subject header, as that must be handled specially. +# Keeps To, Cc, and Resent- headers if $keeprecip is true. + my ($keeprecip, $nymcommands) = @_; + my (@headers); + my ($key, $val, $present); + + @headers = @anon_headers; + @anon_headers = (); + foreach $field (@deliver_headers) { + ($key, $val) = &parse_field ($field); + if ($key =~ /^(mime\-version|content\-type|newsgroups|x\-anon\-to)$/i + || $key =~ /^(content\-transfer\-encoding|in\-\reply\-to)$/i + || $key =~ /^(references)$/i + || $keeprecip && $key =~ /^(resent-)?(to|cc)$/i + || $nymcommands && $key =~ /^nym-commands?/i) { + push (@headers, $field); + } + } + @deliver_headers = (); + if ($config{'default-reply-to'}) { + ($val, $present) = &lookup_val ('reply-to', @headers); + if (!$present) { + push (@headers, "Reply-To: $val\n"); + } + } + return @headers; +} + +# End of routines for dealing with anonymous remailer chains. + +sub deliver { +# &deliver ($body, $prefix, @the_recips) +# Deliver the message composed of (@deliver_headers, $header_sep, $prefix, +# $body) to the @the_recips. + my ($body, $prefix, @the_recips) = @_; + my ($invoc, $line, $lineno); + my (%mark, %mark2); + my ($d_resent, $strip_recip); + my (@field_recips, $any_recips, $new_field); + my ($tmpfile); + my (@old_deliver_headers); + + $deliver_debug = 0; + if ($post || $edit && !$prezilla) { + foreach $recip (@the_recips) { + $mark{&strip_address ($recip)} = 1; + &pdv ("Marked $recip\n"); + } + $d_resent = 0; + foreach (@deliver_headers) { + ($key, $val) = &parse_field ($_); + if ($key =~ # source: sendmail 8.6.8 conf.c + /^resent\-(sender|from|reply\-to|to|cc|bcc|message\-id|date)$/i) { + $d_resent = 1; + } + } + $any_recips = 0; + if ($d_resent) { + @deliver_headers = &delete_field ("resent-bcc", @deliver_headers); + } else { + @deliver_headers = &delete_field ("bcc", @deliver_headers); + } + @old_deliver_headers = @deliver_headers; + foreach (@old_deliver_headers) { + ($key, $val) = &parse_field ($_); + @field_recips = (); + if ($d_resent && $key =~ /^resent\-(to|cc)$/i + || !$d_resent && $key =~ /^(to|cc)$/i) { + # follows sendmail 8.6.8 conf.c except for 'apparently-to' + &pdv ("key = $key, val = $val\n"); + foreach $recip (&split_commas ($val)) { + &pdv ("Scanned $recip\n"); + $strip_recip = &strip_address ($recip); + if ($mark{$strip_recip}) { + push (@field_recips, $recip); + } + $mark2{$strip_recip} = 1; + } + if ($#field_recips >= 0) { + @deliver_headers = &replace_field (&format_header + ($key, @field_recips), + @deliver_headers); + $any_recips = 1; + } else { + @deliver_headers = &delete_field ($key, @deliver_headers); + } + } + } + # Construct the difference set - recipients not in headers. + @field_recips = (); + foreach $recip (@the_recips) { + if (!$mark2{&strip_address ($recip)}) { + push (@field_recips, &strip_address ($recip, 1)); + } + } + if ($#field_recips >= 0) { + if ($any_recips) { + $new_field = 'Bcc'; + } else { + $new_field = 'To'; + } + if ($d_resent) { + $new_field = 'Resent-'.$new_field; + } + push (@deliver_headers, &format_header ($new_field, + @field_recips)); + } + &pdv (@deliver_headers); + # Note: could do more checking here. However, consistent with usage. + if ($post) { + $tmpfile = 'premail.tmp'.$$; + } else { + $tmpfile = &tmp_filename (); + } + open (DELIVER, '>'.$tmpfile) || + die "Couldn't open file: $!"; + } else { + # we know it's sendmail + $invoc = &bin_sendmail (); + if ($#sendmail_args >= 0) { + $invoc .= ' '.join (' ', @sendmail_args); + } + $invoc .= ' -oi'; + foreach $recip (@the_recips) { + $recip = &shell_quote (&strip_address ($recip, 1)); + $invoc .= ' '.$recip; + } + $deliver_debug = ($config{'debug'} =~ /[yp]/); + if ($deliver_debug || $config{'storefile'}) { + $invoc .= ' << -eof-'; + if (!$deliver_debug) { + open (DELIVER, '>>' + .&tilde_expand_mkdir ($config{'storefile'})) || + die "Couldn't open file: $!"; + } + &deliver_line ($invoc."\n"); + } else { + open (DELIVER, '|'.$invoc) || + die "Couldn't open file: $!"; + } + } + foreach (@deliver_headers) { + &deliver_line ($_); + } + if ($header_sep) { + &deliver_line ($header_sep); + } + &deliver_line ($prefix); + &open_body ($body); + $lineno = 0; + while (defined ($line = &get_line_body ($body))) { + if ($lineno == 0 && $config{'extrablank'} && $line =~ /^\:/) { + &deliver_line ("\n"); + } + &deliver_line ($line); + $lineno++; + } + &close_body ($body); + if ($post) { + close (DELIVER); + my $ppost = &tilde_expand ($config{'post'}); + # bfulgham 7/1/99 -- another EDM fix, checks for undefined + # value on return + if (!defined($ppost) || $ppost eq '') { + $ppost = "/usr/lib/mh/post"; + } + system ($ppost, @post_args, $tmpfile); + unlink $tmpfile; + } elsif ($edit && !$prezilla) { + close (DELIVER); + if ($editfile eq '-') { + open (CAT, $tmpfile) || + die "Couldn't open file: $!"; + while () { print; } + close (CAT); + &delete_tmpfile ($tmpfile); + } else { + rename ($editfile, $editfile.'~'); + rename ($tmpfile, $editfile); + } + } elsif ($deliver_debug || $config{'storefile'}) { + &deliver_line ('-eof-'."\n"); + if (!$deliver_debug) { close (DELIVER); } + } else { + close (DELIVER); + if ($? && $error_mode =~ /^[mpdew]$/) { + $error_mode = 'd'; + &error (""); + } + } +} + +sub deliver_line { +# &deliver_line ($line) +# Deliver a line. Implements output multiplexing to debug or DELIVER. The +# "line" may actually be multiple lines with no problem. + if (!$post && !$edit && $deliver_debug) { + &pdebug (@_); + } else { + print DELIVER @_; + } +} + +########################################## +# parsing of e-mail addresses & aliases + +sub parse_address { +# @tokens = &parse_address ($addr) +# Parse the address into e-mail addresses, items in parentheses, items in +# angle brackets, quoted items. Whitespace and commas get their own tokens. +# +# Based on RFC 822. + my ($addr) = @_; + my (@tokens); + my ($paren, $brack, $quote, $backslash); + my ($token); + + @tokens = (); + $paren = 0; + $brack = 0; + $quote = 0; + $backslash = 0; + $token = ''; + foreach $char (split (//, $addr)) { + if (!$paren && !$brack && !$backslash && !$quote && $char ne ' ' + && $token =~ /^ +$/) { + push (@tokens, $token); $token = ''; + } + if ($backslash) { $token .= $char; $backslash = 0; } + elsif ($char eq '\\') { $token .= $char; $backslash = 1; } + elsif ($char eq '"') { + if (!$quote && !$paren && !$brack && $token ne '') { + push (@tokens, $token); $token = ''; + } + $token .= $char; + $quote = !$quote; + if (!$quote && !$paren && !$brack) { + push (@tokens, $token); $token = ''; + } + } + elsif ($quote) { $token .= $char; } + elsif ($char eq '<' || $char eq '(') { + if (!$paren && !$brack && $token ne '') { + push (@tokens, $token); $token = ''; + } + $token .= $char; + $brack++ if $char eq '<'; + $paren++ if $char eq '('; + } + elsif ($char eq '>' || $char eq ')') { + $token .= $char; + $brack-- if $char eq '>'; + $paren-- if $char eq ')'; + if (!$paren && !$brack) { + push (@tokens, $token); $token = ''; + } + } + elsif (!$paren && !$brack && $char eq ',') { + if ($token ne '') { push (@tokens, $token); } + push (@tokens, $char); + $token = ''; + } + elsif (!$paren && !$brack && $char eq ' ') { + if ($token !~ /^ *$/) { push (@tokens, $token); $token = ''; } + $token .= $char; + } + else { $token .= $char; } + } + push (@tokens, $token) if $token ne ''; + if ($paren) { + &error ("Address $addr left a parenthesis open\n"); + } elsif ($brack) { + &error ("Address $addr left an angle bracket open\n"); + } elsif ($quote) { + &error ("Address $addr left a quote mark open\n"); + } elsif ($backslash) { + &error ("Address $addr left a backslash open\n"); + } + return (@tokens); +} + +sub split_commas { +# @addrs = &split_commas ($items) + my ($items) = @_; + my (@tokens); + my ($addr); + my (@addrs); + + @tokens = &parse_address ($items); + @addrs = (); + foreach $token (@tokens) { + if ($token eq ',') { + $addr =~ s/^\s+//s; + $addr =~ s/\s+$//s; + if ($addr ne '') { push (@addrs, $addr); } + $addr = ''; + } + else { $addr .= $token; } + } + # bfulgham, 7-1-99: EDM suggestion -- check for + # a defined $addr before manipulating + if (defined($addr)) { + $addr =~ s/^\s+//s; + $addr =~ s/\s+$//s; + if ($addr ne '') { push (@addrs, $addr); } + } + return (@addrs); +} + +sub strip_caret { +# ($strip, $caret) = &strip_caret ($raw) +# Strip the carets off the address, no other processing. +# +# A new feature (as of 0.44) is to allow comma-separated caret commands +# inside double parentheses. +# +# The second through fourth cases are to undo Netscape's helpful-seeming +# conversion into more RFC-822-like syntax. + my ($items) = @_; + my (@tokens); + my ($addr); + my (@addrs); + my ($strip, $caret); + my ($strip_rec, $caret_rec); + my ($caretmode); + + @tokens = &parse_address ($items); + $strip = ''; + $caret = ''; + foreach $token (@tokens) { + if ($caretmode) { + $caret .= $token; + } elsif ($token =~ /^\(\((.+)\)\)$/) { + $caret .= '^'.join ('^', &split_commas ($1)); + } elsif ($token =~ /^\"\(\^?(.+)\)\"$/) { + $caret .= '^'.join ('^', &split_commas ($1)); + } elsif ($token =~ /^\"(\(\(.*|.*\)\))\"$/) { + ($strip_rec, $caret_rec) = &strip_caret ($1); + if ($strip_rec ne '') { $strip .= '"'.$strip_rec.'"'; } + $caret .= $caret_rec; + } elsif ($token =~ /^\<\"(.*\S)\s*\(\((.+)\)\)\"\>$/) { + $strip .= '<"'.$1.'">'; + $caret .= '^'.join ('^', &split_commas ($2)); + } elsif ($token =~ /^\<([^\^]*)(\^.*)\>$/) { + $strip .= '<'.$1.'>'; + $caret .= $2; + } elsif ($token =~ /^([^\^]*)(\^.*)$/) { + $strip .= $1; + $caret .= $2; + $caretmode = 1; + } else { + $strip .= $token; + } + } + $strip =~ s/^\s+//s; + $strip =~ s/\s+$//s; + return ($strip, '') if ($config{'no-caret'}); + return ($strip, $caret); +} + +sub strip_address { +# $stripped_addr = &strip_address ($full_addr) +# Strips off comments, names, and caret commands. Based on RFC 822 +# conversion of mailbox to [route] addr-spec. Also converts to lower +# case, the idea being that it is ok to compare stripped addresses +# as strings. +# +# This is not perfect wrt RFC 822 spec, but should do fine in practice. +# +# If an optional second argument is given, then the lowercase conversion +# is not performed. + my ($addr) = @_; + my ($nocaret, $carets, $result); + + ($nocaret, $carets) = &strip_caret ($addr); + $inside = ''; + $outside = ''; + foreach $token (&parse_address ($nocaret)) { + if ($token =~ /^\<(.+)\>$/) { + $inside .= $1; + } elsif ($token !~ /^\(.*\)$/ && $token !~ /^\".*\"$/ + && $token !~ /^ +$/) { + $outside .= $token; + } + } + if ($inside ne '') { $result = $inside; } + else { $result = $outside; } + if ($#_ < 1) { $result = lc $result; } + return $result; +} + +sub strip_and_join { +# $join = &strip_and_join (@addresses) +# Strip each address (preserving case), and join with commas + my (@in) = @_; + my (@out); + + @out = (); + foreach (@in) { + push (@out, &strip_address ($_, 1)); + } + return join (',', @out); +} + +# A note on aliases. Expanded aliases should never have commas in them, +# therefore the use of split and join is completely ok. At the moment, +# there is no checking for commas (say, in comment fields, etc.). More +# bulletproofing might be added later. +# +# A different approach would have been to use perl5 anonymous arrays, +# but I decided against that in case I had to make a perl4 version. + +sub clear_alias { +# Reset all alias expansion data structures. + %ealias = (); +} + +sub expand_alias { +# (@expansion) = &expand_alias (@raw) +# Expand aliases of @raw. Only call this function once for each recipient +# without calling clear_alias in between - otherwise the duplication +# checking code will kick in and you will get a null expansion. + my ($stripped, $caret, @expand, @result); + my ($eaddr, $ecaret); + + @result = (); +# print ("enter args = (".join (', ', @_).")\n"); + foreach $raw (@_) { + ($stripped, $caret) = &strip_caret ($raw); + $stripped = &strip_address ($stripped); +# print "/".$stripped.'/ {'.$ealias{$stripped}."}\n"; +# print " \$alias\{$stripped\} = $alias{$stripped}\n"; + if (defined $ealias{$stripped}) { @expand = (); } # already seen it + elsif ($alias{$stripped}) { + @expand = (); + foreach $exp (&split_commas ($alias{$stripped})) { + ($eaddr, $ecaret) = &strip_caret ($exp); +# print " split: $_\n"; + if ($eaddr eq '') { + push (@expand, + &compose_carets ($stripped.$ecaret, $caret)); + } else { + $ealias{$stripped} = "-"; + push (@expand, + (&expand_alias + (&compose_carets ($exp, $caret)))); + } + } + $ealias{$stripped} = join (',', @expand); + } else { # not in alias table + @expand = ($raw); + $ealias{$stripped} = $raw; + } +# print &format_header ("exp_alias expanded", @expand); + push (@result, @expand); + } +# print ("exit result = (".join (', ', @result).")\n"); + return @result; +} + +sub compose_carets { +# $new_addr = &compose_carets ($addr, $carets) +# Add the carets to the addr. When there is a conflict, the new carets take +# precedence. +# +# Note: rewrites to "caret canonical form" with actual carets. We may +# choose to change this to preserve double paren syntax or whatever, so +# that the logs represent what the user asked for. + my ($addr, $caret2) = @_; + my ($strip, $caret1); + my (%caret2); + +# print "composing $addr with $caret2\n"; + ($strip, $caret1) = &strip_caret ($addr); +# print ("$addr, $caret2\n"); + %caret2 = (); + foreach (split (/\^/, &split_caret ($caret2))) { +# print ">$_\n"; + if (/^([\w]+)(\-\w+|)(\=.*|)$/) { +# print "$1 $2 $3\n"; + $caret2{$1} = $3; + } + } + # deal with synonyms + if (defined $caret2{'encrypt'}) { + $caret2{'key'} = $caret2{'encrypt'}; + } elsif (defined $caret2{'key'}) { + $caret2{'encrypt'} = $caret2{'key'}; + } + foreach (split (/\^/, $caret1)) { + if (/^([\w]+)(\-\w+|)(\=.*|)$/) { + if (!defined $caret2{$1}) { + $strip .= '^'.$_; + } + } + } + return $strip.$caret2; +} + +sub split_caret { +# $carets = &split_caret ($caret) +# Convert a caret item into canonical form (i.e. caret separated). The name +# of this routine is a bit of a misnomer. + my ($dummy, $caret) = &strip_caret ($_[0]); + + return $caret; +} + +sub format_header { +# $field = &format_header ($key, @vals) +# Format key and vals (as comma separated list) nicely as per RFC 822. The +# specific rules are: space between comma and next element, three spaces +# on continuing line, no more than 70 columns unless item won't fit, +# compress all whitespace to one space. +# +# I should probably rewrite this in terms of wordwrap. + my ($key, $line, $val, $toobig, $result); + + $result = ''; + $key = shift; + $line = $key.':'; + $toobig = 0; + while ($#_ >= 0) { + $val = ' '.shift; + $val =~ s/\s+/ /sg; + if ($#_ >= 0) { $val .= ','; } + if ((length $line) + (length $val) > 70) { + $result .= $line."\n"; + $line = ' '.$val; + } else { + $line .= $val; + } + } + return $result .= $line."\n"; +} + +########################################## +# error handling + +sub error { +# &error ($error_string) +# +# In error mode "m", this routine will try to mail back the original +# message, but it doesn't always succeed, because the message might not +# be around any more. + my ($error_msg) = @_; + my ($new_body, $line); + my ($dead_letter); + + if ($error_mode eq 'm') { + @deliver_headers = ("To: $ENV{'USER'}\n", + "Subject: premail error: undelivered mail\n", + "Mime-Version: 1.0\n", + "Content-Type: multipart/mixed; boundary=\"_\"\n"); + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || + die "Couldn't open file: $!"; + print NEW "--_\n"; + print NEW "\n"; + print NEW $error_msg; + print NEW "\n"; + print "in_body = $in_body.\n"; + print NEW "--_\n"; + print NEW "Content-Type: message/rfc822\n"; + print NEW "\n"; + foreach $line (@in_headers) { + print NEW $line; + } + if ($header_sep) { + print NEW "\n"; + &open_body ($in_body); + while (defined ($line = &get_line_body ($in_body))) { + print NEW $line; + } + &close_body ($in_body); + } + print NEW "\n"; + print NEW "--_--\n"; + close (NEW); + $post = 0; + $edit = 0; + delete $config{'storefile'}; + &deliver ($new_body, '', $ENV{'USER'}); + } elsif ($error_mode eq 'p') { + print STDERR $error_msg; + $dead_letter = &tilde_expand ($config{'dead-letter'}); + print STDERR "Saving message in $dead_letter\n"; + open (DEAD, '>>'.$dead_letter) || + die "Couldn't open a dead letter file: $!"; + print DEAD (("From $ENV{'USER'} ".localtime)."\n"); + foreach $line (@in_headers) { + print DEAD $line; + } + if ($header_sep) { + print DEAD "\n"; + &open_body ($in_body); + while (defined ($line = &get_line_body ($in_body))) { + print DEAD $line; + } + &close_body ($in_body); + } + print DEAD "\n"; + close (DEAD); + } elsif ($error_mode eq 's') { + $error_msg =~ s/^([^\n]*)\n/$1/s; + print "521 $error_msg, closing connection\n"; + } elsif ($error_mode eq 'g') { + $error_msg =~ s/\n$//s; + $error_msg = "\n".$error_msg; + $error_msg =~ s/\n/\n500 /s; + $error_msg =~ s/^\n//s; + $error_msg .= "\n"; + print STDERR $error_msg; + } else { + print STDERR $error_msg; + } + &delete_open_tmpfiles (); + exit 1; +} + +# debug output and logging + +sub pdebug { +# &pdebug ($msg) + if ($config{'debug'} =~ /l/) { + print LOG @_; + } else { + print STDERR @_; + } +} + +sub pdv { +# &pdv ($msg) +# Only print debug if verbose is set. Returns undef to allow return &pdv (msg) +# idiom. + if ($config{'debug'} =~ /v/) { + &pdebug (@_); + } + return undef; +} + +sub pdi { +# &pdi ($msg) +# Prints or logs the message if verbose or interactive. + my ($msg) = @_; + + if ($interactive) { + print STDERR ($msg); + } + if ($config{'debug'} =~ /v/ && ($config{'debug'} =~ /l/ || !$interactive)){ + &pdebug ($msg); + } +} + +sub pfi { +# &pfi ($msg) +# Prints or logs the message if verbose or interactive. Word-wraps the +# message. + my ($msg) = @_; + + &pdi (&wordwrap ($msg, 71, ' ')); +} + +sub wordwrap { +# $newmsg = &wordwrap ($msg, $len, $prefix) + my ($msg, $len, $prefix) = @_; + my ($newmsg, $msgline); + + $newmsg = ''; + $msgline = ''; + $msg =~ s/\s*$//; + foreach $word (split (/\s/, $msg)) { + if ((length $msgline) + 1 + (length $word) <= $len) { + if ($msgline ne '') { $msgline .= ' '; } + $msgline .= $word; + } else { + if ($msgline ne '') { $newmsg .= $msgline."\n"; } + $msgline = $prefix.$word; + } + } + return $newmsg.$msgline."\n"; +} + +########################################## +# utility functions + +# functions for manipulating dict forms +# Dict form is a Perl array in which each element represents an RFC 822 +# field, except that LF is used in place of CRLF. + +sub lookup_val { +# ($val, $present) = &lookup_val ($key, @dict) +# Look up the key in the dict +# Return ($val, 1) if found, ("", 0) if not. + my ($key, @dict) = @_; + my ($field_key, $field_val); + + foreach $field (@dict) { + ($field_key, $field_val) = &parse_field ($field); + if (lc $field_key eq lc $key) { + return ($field_val, 1); + } + } + return ("", 0); +} + +sub parse_field { +# ($key, $val) = &parse_field ($key) + if ($_[0] =~ /^([!-9\;-\177]+)\:\s*(.*)\n$/s) { # RFC 822 field + return ($1, $2); + } else { + &error ("premail internal error (parse_field): field is:\n$field"); + } +} + +sub delete_field { +# (@new_dict) = &delete_field ($key, @dict) + my ($key, @dict) = @_; + my (@new_dict); + my ($field_key, $field_val); + + @new_dict = (); + foreach $field (@dict) { + ($field_key, $field_val) = &parse_field ($field); + if (lc $field_key ne lc $key) { + push (@new_dict, $field); + } + } + return (@new_dict); +} + +sub replace_field { +# (@new_dict) = &replace_field ($new_field, @dict) +# Delete the field if it already exists, and append to the end. + my ($field, @dict) = @_; + my ($key, $val); + + ($key, $val) = &parse_field ($field); + @dict = &delete_field ($key, @dict); + push (@dict, $field); + return (@dict); +} + +# + +sub member { +# $bool = &member ($el, @list) +# Perform membership test of $el in @list. + my ($el, @list) = @_; + foreach (@list) { + if ($_ eq $el) { return 1; } + } + return 0; +} + +# + +sub tilde_expand { +# $file_name = &tilde_expand ($file_name) +# Expand filenames of the form ~/file. Also expand $< sequence (uid). + my ($file_name) = @_; + + # bfulgham, 7-1-99: Another EDM fix. If file_name is undefined, + # return immediately to avoid error messages. + return $file_name unless defined($file_name); + + if ($file_name =~ /^\~[^\/]/) { + &error ("premail can't handle ~user/ form in $file_name, use ~/ or\n". + "full path name instead\n"); + } + $file_name =~ s/^\~/$ENV{"HOME"}/; + $file_name =~ s/\$\ $now || $mtime + $lifetime <= $now); +} + +sub time { +# $time = &time (gmttime (time)) +# Format an (already expanded time) nicely. + my (@time) = @_; + my $time; + + $time = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")[$time[6]]; + $time .= sprintf (', %02d ', $time[3]); + $time .= ("Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[$time[4]]; + $time .= " $time[5]"; + $time .= sprintf (" %d:%02d:%02d", $time[2], $time[1], $time[0]); + $time .= ' GMT'; + return $time; +} + +sub tmp_filename { +# $tmp_filename = &tmp_filename (suffix) +# Return the name for a new temp file (and add to @open_tmpfiles). +# Reference count is set to one. + my ($suffix) = @_; + my $base; + my $fn; + + $tmpfile_count++; + $base = &tilde_expand ($config{'tmpdir'}); + $base =~ s/([^\/])$/$1\//; + $base .= 'premail.'.$$.'.'; + $fn = $base . $tmpfile_count; + $fn .= $suffix if $suffix; + while (!sysopen(TMPFH,$fn,&O_RDWR|&O_CREAT|&O_EXCL,0600) && $tmpfile_count < 32000) { + $tmpfile_count++; + $fn = $base . $tmpfile_count; + $fn .= $suffix if $suffix; + } + die "Can't open temp file: $!\n" if ($tmpfile_count >= 32000); + close (TMPFH); + push (@open_tmpfiles, $fn); + $tmpfile_refcnt{$fn} = 1; + return $fn; +} + +sub refcnt_bump { +# &refcnt_bump ($body, $n) +# Add $n to the reference count of $body. Delete if reference count reaches +# zero. + my ($body, $n) = @_; + + &pdv ("refcnt_bump ($body, $n) $tmpfile_refcnt{$body}\n"); + $tmpfile_refcnt{$body} += $n; + if ($tmpfile_refcnt{$body} < 1) { + &delete_tmpfile ($body); + } +} + +sub delete_tmpfile { +# &delete_tmpfile ($filename) + my ($fn) = @_; + my @new_open_tmpfiles; + + foreach $tmpfile (@open_tmpfiles) { + if ($tmpfile eq $fn) { unlink $fn; } + else { push (@new_open_tmpfiles, $tmpfile); } + } + undef $tmpfile_refcnt{$fn}; + @open_tmpfiles = @new_open_tmpfiles; +} + +sub delete_open_tmpfiles { + foreach $tmpfile (@open_tmpfiles) { + &pdv ("Deleting $tmpfile\n"); + unlink $tmpfile; + } + &pgp_alldone (); +} + +sub read_and_delete { + my ($file) = @_; + my (@data); + + $data = ''; + if (open (ERRFILE, $file)) { + while () { + # print $_; # Removed 9-7-1999 BFulgham to allow filter use + $data .= $_; + } + close (ERRFILE); + } + &delete_tmpfile ($file); + return $data; +} + +sub add_terminating_newline { +# &add_terminating_newline ($file) +# If $file does not end with a newline, add one. (This is a hack for early +# Mozilla beta integration). + my ($file) = @_; + my ($c); + + open (F, $file) || die "Can't open $file: $!"; + seek (F, (-s $file) - 1, 0); + sysread (F, $c, 1); + close (F); +# print "Trailing character is really ".unpack ('c', $c)."\n"; + if ($c ne "\n") { + open (F, '>>'.$file) || die "Can't open file: $!"; + print F "\n"; + close F; + } +} + +########################################## +# invoking PGP + +# This section is not as clean or elegant as I might like, but it does +# get the job done. + +sub pgp_encrypt { +# ($out_body, $err) = &pgp_encrypt +# ($body, $prefix, $sign, $signuser, $pubring, @keys) +# Encrypt ($prefix, $body) with @keys. Optionally sign (if $sign) with +# $signuser (the responsibility for obtaining the password lies below +# this interface). +# +# $err is the string returned. + print "I'm going into pgp now\n"; + my ($body, $prefix, $sign, $signuser, $pubring, @keys) = @_; + my ($outfile, $errfile); + my ($invoc, $status, $line, $pass, $pr, $sr); + + if ($config{'debug'} =~ /y/) { + return &fake_pgp_encrypt + ($body, $prefix, $sign, $signuser, $pubring, @keys); + } + $outfile = &tmp_filename (); + $errfile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + if ($sign eq 'error') { + &error ("$signuser\n"); + } elsif ($sign eq 'header') { + $sign = ''; + } elsif ($sign eq 'ring') { + &error ("No keyring in $premail_secrets for $signuser\n") + unless $pgpring{$signuser}; + ($pr, $sr) = &makerings ($pgpring{$signuser}, $pubring, @keys); + $invoc .= " +secring=$sr"; + $signuser = '0x'; + $pubring = $pr; + $pass = ''; + } + if ($pubring) { $invoc .= ' +pubring='.&shell_quote ($pubring); } + + # TEMP FIX -- Language support. Will be updated for others + $invoc .= ' +language=en +comment= -feat'; + if ($sign) { + $invoc .= 's -u '.&shell_quote ($signuser); + &load_secrets (); + unless (defined $pass) { + if (defined $pgppass{$signuser}) { + $pass = $pgppass{$signuser}; + } else { + &error ("No passphrase in $premail_secrets for" + ." $signuser\n"); + } + } + } + foreach $key (@keys) { + $invoc .= ' '.&shell_quote ($key); + } + $invoc .= ' > '.$outfile; + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking PGP as $invoc\n"); + $status = &open_pgp ($invoc, $pass, 'w'); + if (!$status) { &error ("Error in PGP encryption!\n"); } + print PGP $prefix; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print PGP $line; + } + close (PGP); + $status = $?; + $pr && &delete_tmpfile ($pr); + $sr && &delete_tmpfile ($sr); + $err = &read_and_delete ($errfile); + if ($status) { &error ("PGP error\n$err"); } + &pdv ($err); + # defer close body 'til after error + &close_body ($body); + return ($outfile, $err); +} + +sub fake_pgp_encrypt { + my ($body, $prefix, $sign, $signuser, $pubring, @keys) = @_; + my ($outfile, $keys, $line); + + $outfile = &tmp_filename (); + open (OUT, '>'.$outfile) || die "Couldn't open file: $!"; + if ($sign) { + $sign = " (sign $signuser)"; + } + $keys = join (' ', @keys); +# if ($pubring) { print OUT "pubring\=$pubring\n"; } + print OUT "-----BEGIN PGP MESSAGE-----$sign $keys\n"; + print OUT $prefix; + open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print OUT $line; + } + &close_body ($body); + print OUT "-----END PGP MESSAGE-----\n"; + close (OUT); + return ($outfile, "fake!\n"); +} + +sub pgp_clearsign { +# ($out_body, $err) = &pgp_clearsign ($body, $prefix, $signuser, $sign_type) +# Encrypt ($prefix, $body) sign with user $signuser (the responsibility +# for obtaining the password lies below this interface). +# +# $err is the string returned. + my ($body, $prefix, $signuser, $sign_type) = @_; + my ($outfile, $errfile); + my ($invoc, $status, $line, $pass, $pr, $sr); + + $sign_type = 'sign' unless $sign_type; + $outfile = &tmp_filename (); + $errfile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + &error ("$signuser\n") if ($sign_type eq 'error'); + &load_secrets (); + if ($sign_type eq 'ring') { + &error ("No keyring in $premail_secrets for $signuser\n") + unless $pgpring{$signuser}; +# Clean: $pubring and @keys aren't defined, so should probably be blank. + ($pr, $sr) = &makerings ($pgpring{$signuser}, $pubring, @keys); + $invoc .= " +secring=$sr +pubring=$pr"; + $signuser = '0x'; + $pass = ''; + } + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +comment= -fats +clearsig=on'; + $invoc .= ' -u '.&shell_quote ($signuser); + unless (defined $pass) { + if (defined $pgppass{$signuser}) { + $pass = $pgppass{$signuser}; + } else { + &error ("No passphrase in $premail_secrets for $signuser\n"); + } + } + $invoc .= ' > '.$outfile; + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking PGP as $invoc\n"); + $status = &open_pgp ($invoc, $pass, 'w'); + if (!$status) { &error ("Error invoking PGP!\n"); } + print PGP $prefix; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print PGP $line; + } + close (PGP); + $status = $?; + $pr && &delete_tmpfile ($pr); + $sr && &delete_tmpfile ($sr); + $err = &read_and_delete ($errfile); + if ($status) { &error ("PGP error\n$err"); } + &pdv ($err); + &close_body ($body); + return ($outfile, $err); +} + +sub pgp_mime_sign { +# ($out_body, $err, $boundary) = &pgp_mime_sign ($body, $prefix, $signuser) +# Encrypt ($prefix, $body) sign with user $signuser (the responsibility +# for obtaining the password lies below this interface). +# +# $err is the string returned. + print "I'm using the mime-pgp signing routine.\n"; + my ($body, $prefix, $signuser, $sign_type) = @_; + my ($outfile, $errfile, $mimefile); + my ($invoc, $status, $line, $pass, $boundary); + + $boundary = &random (80); + $outfile = &tmp_filename (); + $errfile = &tmp_filename (); + $mimefile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + &error ("$signuser\n") if ($sign_type eq 'error'); + &load_secrets (); + if ($sign_type eq 'ring') { + &error ("No keyring in $premail_secrets for $signuser\n") + unless $pgpring{$signuser}; +# Clean: $pubring and @keys aren't defined, so should probably be blank. + ($pr, $sr) = &makerings ($pgpring{$signuser}, $pubring, @keys); + $invoc .= " +secring=$sr +pubring=$pr"; + $signuser = '0x'; + $pass = ''; + } + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +comment= -fabst'; + $invoc .= ' -u '.&shell_quote ($signuser); + unless (defined $pass) { + if (defined $pgppass{$signuser}) { + $pass = $pgppass{$signuser}; + } else { + &error ("No passphrase in $premail_secrets for $signuser\n"); + } + } + $invoc .= ' > '.$outfile; + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking PGP as $invoc\n"); + $status = &open_pgp ($invoc, $pass, 'w'); + if (!$status) { &error ("Error invoking PGP!\n"); } + &open_body ($body); + open (NEW, '>'.$mimefile) || die "Couldn't open file: $!"; + print NEW "This message is in PGP/MIME format, according to the" + ." Internet Draft\n"; + print NEW "draft-elkins-pem-pgp-04.txt. For more information, see:\n"; + print NEW "http://www.c2.net/~raph/pgpmime.html\n"; + print NEW "\n"; + print NEW "--$boundary\n"; + $prefix = &canonicalize_line_enc ($prefix); + print NEW $prefix; + print PGP $prefix; + while (defined ($line = &get_line_body ($body))) { + $line = &canonicalize_line_enc ($line); + print NEW $line; + print PGP $line; + } + close (PGP); + $status = $?; + $pr && &delete_tmpfile ($pr); + $sr && &delete_tmpfile ($sr); + $err = &read_and_delete ($errfile); + if ($status) { &error ("PGP error\n$err"); } + &pdv ($err); + &close_body ($body); + print NEW "\n"; + print NEW "--$boundary\n"; + print NEW "Content-Type: application/pgp-signature\n"; + print NEW "\n"; + if (open (OUTFILE, $outfile)) { + while () { + s/PGP MESSAGE/PGP SIGNATURE/; + print NEW $_; + } + close (OUTFILE); + } + print NEW "\n"; + print NEW "--$boundary--\n"; + close (NEW); + &delete_tmpfile ($outfile); + return ($mimefile, $err, $boundary); +} + +my ($PUBRING, $SECRING); +sub pgp_decrypt { +# ($out_body, $err) = &pgp_decrypt ($body, $pass) +# Try to decrypt $body using passphrase $pass. $out_body is null on error. +# +# $err is the string returned. + my ($body, $pass) = @_; + my ($outfile, $errfile); + my ($invoc, $status, $line, $pr, $sr); + + $outfile = &tmp_filename (); + $errfile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode=on'; + $invoc .= " +pubring=$PUBRING" if $PUBRING; + $invoc .= " +secring=$SECRING" if $SECRING; +# if ($pass =~ /^RING$;/) { +# ($pr, $sr) = &makerings ($pass); +# $pass = ''; +# $invoc .= " +pubring=$pr +secring=$sr"; +# } + $invoc .= ' -f'; + $invoc .= ' > '.$outfile; + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking PGP as $invoc\n"); + $status = &open_pgp ($invoc, $pass, 'w'); + if (!$status) { &error ("Error in PGP decryption!\n"); } + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print PGP $line; + } + close (PGP); + $status = $?; + $pr && &delete_tmpfile ($pr); + $sr && &delete_tmpfile ($sr); + &pdv ("Status returned from PGP decryption: $status\n"); + $err = &read_and_delete ($errfile); + &pdv ($err); +# print STDERR $err; +# exit 0; + if ($status < 0 || $status >= 512) { + # status code 1 (<<8) means bad signature; do not reject + &delete_tmpfile ($outfile); + $outfile = ''; + } + # defer close body 'til after error + &close_body ($body); + return ($outfile, $err); +} + +sub pgp_verify { +# ($err) = &pgp_verify ($signed_file, $pgp_file) +# Try to verify signature of $signed_file (using signature in $pgp_file). +# +# $err is the string returned (or null on error). + my ($signed_file, $pgp_file) = @_; + my ($outfile, $errfile); + my ($invoc, $status, $line); + + $errfile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode=on '; + $invoc .= ' '.$pgp_file; + $invoc .= ' '.$signed_file; + $invoc .= ' > '.$errfile.' 2>&1'; + &pdv ("Invoking PGP as $invoc\n"); + $status = &open_pgp ($invoc, '', ''); + $err = &read_and_delete ($errfile); + if (!$status) { + &error ("Error in PGP verification!\n$err"); + } + &pdv ($err); + return ($err); +} + +sub open_pgp { +# $success = &open_pgp ($invoc, $pass, $mode) +# Invoke PGP, opening it as file descriptor PGP, in either read or write +# mode, depending on the value of $mode ('r' or 'w'). Also, convey the +# passphrase. If $mode is '', then don't open it as a pipe, just invoke. +# +# The PGPPASSFD code makes the assumption that the PGP process will read +# the passphrase at its first opportunity, i.e. before reading input. For +# PGP 2.6.2, I've confirmed that the assumption is valid. If not, deadlock +# is a possiblity, although I have a funny feeling that most Unix +# implementations won't block on closing a pipe even if it's not empty. +# +# Instead of merely setting TMP to be $config{'tmpdir'}, we make a +# special PGP temp subdirectory, on a per-process basis (this assumes +# that each process invokes only one PGP at a time, which is safe given +# the relentless file-file orientation of this version of premail). + + my ($invoc, $pass, $mode) = @_; + + if ($mode eq 'r') { $invoc = $invoc.'|'; } + else { $invoc = '|'.$invoc; } + if (!$pgp_tmpdir) { + $pgp_tmpdir = &tilde_expand ($config{'tmpdir'}); + $pgp_tmpdir =~ s/([^\/])$/$1\//; + $pgp_tmpdir .= 'premail.'.$$.'.pgptmp'; + if (!mkdir ($pgp_tmpdir, 0700)) { + &error ("$! creating PGP temp directory"); + } + } + $ENV{'TMP'} = $pgp_tmpdir; + if ($pass) { + pipe (READER, WRITER); + $ENV{'PGPPASSFD'} = fileno(READER); + } + $status = open (PGP, $invoc); + $ENV{'PGPPASSFD'} = ''; + if ($status && $pass) { + syswrite (WRITER, $pass."\n", 1 + length $pass); + } + if ($mode eq '') { + close (PGP); + $status &&= !($? < 0 || $? >= 512); + } + if ($pass) { + # This leaves READER open, but we'll just let that slide. + # If we closed it now, there would be a race condition. + close (WRITER); + } + return $status; +} + +sub pgp_alldone { +# Call after the very last usage of PGP. Deletes PGP temp directory + if ($pgp_tmpdir) { + if (!rmdir ($pgp_tmpdir)) { + &error ("$! removing PGP temp directory\n"); + } + } + $pgp_tmpdir = ''; +} + +sub random { +# $string = &random ($bits) +# Return a string with $bits of entropy. +# +# This routine first calls PGP with the +makerandom option. If that fails, +# then it uses PGP to encrypt some clock-derived pseudorandom numbers. +# Only call when there is no body open, and no PGP open. + my ($bits) = @_; + my ($inf, $outf, $i, $chars_needed); + my (@window); + my ($status); + + # Try makerandom + $outf = &tmp_filename (); + $chars_needed = 2 + sprintf ("%d", $bits / 8); + &pdv ($config{'pgp'}." +makerandom=$chars_needed $outf" + ." >/dev/null 2>&1\n"); + + # TEMP FIX for language -- will be updated + $status = system $config{'pgp'}." +language=en +makerandom=$chars_needed $outf" + ." >/dev/null 2>&1"; + &pdv ($status."\n"); + if (!$status) { + open (RAND, $outf); + $randbytes = ""; + if ($chars_needed == sysread (RAND, $randbytes, $chars_needed)) { + close (RAND); + &delete_tmpfile ($outf); + $chars_needed = sprintf ("%d", ($bits + 5) / 6); + return substr (&encode_base64 ($randbytes), 0, $chars_needed); + } + close (RAND); + } + &delete_tmpfile ($outf); + + foreach $var (keys %ENV) { + &pdv ($var.": ".$ENV{$var}."\n"); + } + # makerandom failed, try roundabout method instead + if (!$config{'signuser'}) { + &error ("Need to set \$config\{\'signuser\'\} to a valid user id in" + ."order to\n" + ."generate randomness!\n"); + } + $inf = &tmp_filename (); + open (INF, '>'.$inf) || die "Couldn't open file: $!"; + for ($i = 0; $i < 256; $i++) { + print INF (rand ())."\n"; + } + close (INF); + ($outf, $err) = &pgp_encrypt + ($inf, '', '', '', '', $config{'signuser'}); + print "$outf\n"; + &delete_tmpfile ($inf); + open (OUTF, $outf) || die "Couldn't open output file: $!"; + @window = (); + while () { + if (/^[A-Za-z0-9\+\/]/) { push (@window, $_); } + if ($#window >= 3) { shift @window; } + } + close (OUTF); + &delete_tmpfile ($outf); + $chars_needed = sprintf ("%d", ($bits + 5) / 6); + if (length $window[0] < $chars_needed) { + &error ("Random: didn't get a long enough string back!\n"); + } + return substr ($window[0], 0, $chars_needed); +} + +########################################## +# premail secrets + +sub load_secrets { +# Load the premail secrets. +# +# This routine needs to do a lot more. +# +# Sets the global variables $secrets_loaded and $premail_secrets + + my ($ps_pgp); + + if (!defined $secrets_loaded) { + $premail_secrets = &tilde_expand ($config{'premail-secrets'}); + $ps_pgp = &tilde_expand ($config{'premail-secrets-pgp'}); + if (!-e $premail_secrets && -e $ps_pgp) { + &do_login (!$interactive); + } + if (-e $premail_secrets) { + open (SECRETS, $premail_secrets) || + die "Couldn't open secrets file: $!"; + while () { + if (/^\s*\$pgppass\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { + $pgppass{$1} = $2; + } elsif (/^\s*\$pgpring\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { + $pgpring{$1} = $2; + } elsif (/^\s*\$ripempass\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { + $ripemuser = $1; + $ripempass{$1} = $2; + } elsif (/\s*\$penetpass\s*\=\s*\'([^\']*)\'/) { + $penetpass = $1; + } elsif (/^\s*\$nym\{\'([^\']+)\'\}\s*\=\s*\'([^\']*)\'/) { + $nym{$1} = $2; + push (@nym_list, $1); + } elsif (/\s*\$premail_pass\s*\=\s*\'([^\']*)\'/) { + $premail_pass = $1; + } + } + close (SECRETS); + } + $secrets_loaded = 1; + } +} + +sub add_secret { +# &add_secret ($secret, $update) +# Add secret to the premail secret file. Assumes secrets are already logged +# in and loaded. If the second argument is given, treat the new secret as +# an update (i.e. overwrite an existing, matching secret if it exists. +# +# One thing I'd like to see this routine do is safely lock the secrets +# file when it's updating it. + my ($secret, $update) = @_; + my ($secret_backup); + my ($match); + + if (!$secrets_loaded) { + &error ("Need to log in to access secrets\n"); + } + if (!-e $premail_secrets) { + if (!sysopen(TOUCH,$premail_secrets,&O_WRONLY|&O_CREAT|&O_EXCL,0600)) { + &error ("Can't open secrets file for writing\n"); + } + &pfi ("Creating secrets file $premail_secrets\n"); + close (TOUCH); + } else { + if (!-o $premail_secrets) { + &error ("Secrets file owned by wrong user.\n"); + } + } + $secret_backup = $premail_secrets.'~'; + rename ($premail_secrets, $secret_backup); + if (!open (SECRET_IN, $secret_backup)) { + &error ("Can't open secret file\n"); + } + if (!open (SECRET_OUT, '>'.$premail_secrets)) { + &error ("Can't update secret file\n"); + } + if ($secret =~ /^(\$\w+\s*\=)/ || + $secret =~ /^(\$\w+\{\'([^\']+)\'\}\s*\=)/) { + $match = $1; + } + while () { + if ($update) { + if (/^(\$\w+\s*\=)/ || /^(\$\w+\{\'([^\']+)\'\}\s*\=)/) { +# print "$match $1\n"; + if ($match eq $1) { + print SECRET_OUT $secret; + $secret = ''; + } else { + print SECRET_OUT $_; + } + } else { + print SECRET_OUT $_; + } + } elsif (/^\$nym\{/ && $secret =~ /^\$nym\{/) { + print SECRET_OUT $secret; + $secret = ''; + print SECRET_OUT $_; + } else { + print SECRET_OUT $_; + } + } + close (SECRET_IN); + if ($secret ne '') { + print SECRET_OUT $secret; + } + close (SECRET_OUT); + &save_secrets (); + unlink $secret_backup; +} + +sub save_secrets { +# Save secrets in encrypted secrets file. + my ($ps, $ps_pgp); + + $ps = &tilde_expand ($config{'premail-secrets'}); + $ps_pgp = &tilde_expand ($config{'premail-secrets-pgp'}); + if ($premail_pass) { + &encrypt_secrets ($ps_pgp, $ps, $premail_pass); + } +} + +sub do_login { +# &do_login ($x) +# Try to login. Fail through &error - login always succeeded on return. + my ($x) = @_; + my ($pass); + my ($ps, $ps_pgp); + my ($status); + my ($done, $triesleft); + + $ps = &tilde_expand ($config{'premail-secrets'}); + $ps_pgp = &tilde_expand ($config{'premail-secrets-pgp'}); + if (-e $ps) { + &error ("Already logged in!\n"); + } + if (!-e $ps_pgp) { + &error ("Can't find encrypted secrets file $ps_pgp\n"); + } + for ($triesleft = 2; !$done && $triesleft; $triesleft--) { + $pass = &getpass ($x); + $status = &decrypt_secrets ($ps_pgp, $ps, $pass); + if (!-s $ps) { unlink $ps; } + $done = (!$status && -e $ps); + } + if (!$done) { + &error ("Error decrypting secrets file\n"); + } +} + +sub getpass { +# $pass = &getpass ($x) +# Get the premail passphrase, either from X or from stdin. + + my ($x) = @_; + my ($pass); + + if ($x) { + # if ($ENV{'DISPLAY'}) { + # require Gtk; + # $pass = create_entry(); + # } + + # This doesn't seem to work right with Debian's latest + # security fixes. Above is a Gtk interface. + if ($ENV{'DISPLAY'}) { + pipe (READER, WRITER); + system 'xterm -geometry 42x4-5-5 -e perl -e \'' + .'system "stty -echo";print "\n";' + .'print " Remember to logout when done.\n";' + .'print " Your premail passphrase, please: ";' + .'open (F, ">&'.fileno(WRITER).'");' + .'print F "".;\''; + close (WRITER); + $pass = ; + close (READER); + } else { + &error ("Can't open X window to get passphrase because DISPLAY is" + ."not set\n"); + } + } else { + $interactive = 1; + system "stty -echo"; + $| = 1; + print "Remember to logout when done.\n"; + print "Your premail passphrase, please: "; + $pass = ; + print "\n"; + system "stty echo"; + } + # We might not always have a newline -- use chomp! + chomp $pass; + return $pass; +} + +sub decrypt_secrets { +# $status = &decrypt_secrets ($ps_pgp, $ps, $pass) + my ($ps_pgp, $ps, $pass) = @_; + my ($invoc, $errfile); + + $errfile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode=on -f'; + $invoc .= ' < '.$ps_pgp; + $invoc .= ' > '.$ps; + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking PGP as $invoc\n"); + if(-e $ps) { + &error ("Premail secrets file already exists\n"); + } + $status = &open_pgp ($invoc, $pass, ''); + $err = &read_and_delete ($errfile); + &pdv ($err); + return !$status; +} + +sub encrypt_secrets { +# &encrypt_secrets ($ps_pgp, $ps, $pass) + my ($ps_pgp, $ps, $pass) = @_; + my ($invoc, $errfile); + + $errfile = &tmp_filename (); + if (-e $ps_pgp) { + unlink $ps_pgp; + } + $invoc = &tilde_expand ($config{'pgp'}); + $invoc .= ' +batchmode=on -cf'; + $invoc .= ' < '.$ps; + $invoc .= ' > '.$ps_pgp; + $invoc .= ' 2> '.$errfile; + &pdv ("Invoking PGP as $invoc\n"); + $status = &open_pgp ($invoc, $pass, ''); + $err = &read_and_delete ($errfile); + &pdv ($err); + if (!$status) { + &error ("Error encrypting secrets file\n$err"); + } +} + +########################################## +# MIME handling + +sub get_mime_fields { +# (@mime_fields) = &get_mime_fields (@header) +# Get the MIME fields (not including the MIME header). No distinction is +# made between MIMEless headers containing the MIME-Version field and +# all the default MIME fields - both return the empty list. +# +# If the field has a default value, does not put it in the header. +# +# This routine could perhaps use a little work. + my (@header) = @_; + my (@mime_fields); + my ($val, $present, $param_val); + my ($type_base, @type_params); + + ($val, $present) = &lookup_val ("MIME-Version", @header); + if (!$present) { return (); } + @mime_fields = (); + ($val, $present) = &lookup_val ("Content-Type", @header); + if ($present) { + ($type_base, @type_params) = &split_mime_params ($val); + if (lc $type_base eq 'text/plain') { + ($param_val, $present) = &get_mime_param ('charset', @type_params); + if ($present && lc $param_val ne 'us-ascii') { + push (@mime_fields, "Content-Type: $val\n"); + } + } else { + push (@mime_fields, "Content-Type: $val\n"); + } + } + ($val, $present) = &lookup_val ("Content-Transfer-Encoding", @header); + if ($present) { + if (lc $val ne '7bit') { + push (@mime_fields, "Content-Transfer-Encoding: $val\n"); + } + } + return (@mime_fields); +} + +sub split_mime_params { +# ($baseval, @mime_params) = &split_mime_params ($val) +# Split the value portion of a MIME field into the base and the +# parameters. +# +# Not quite right yet; doesn't cope with quoted semicolons. +# +# Source: definition of content in RFC 1521 + my ($val) = @_; + + return split (/\s*\;\s*/, $val); +} + +sub get_mime_param { +# ($val, $present) = &get_mime_param ($attribute, @mime_params) +# Get the mime parameter if present. Removes quoting if present. +# +# Source: definition of parameter, attribute, value in RFC 1521 + my ($attribute, @mime_params) = @_; + my ($val, $present); + + foreach $param (@mime_params) { + if ($param =~ /^([^\000- \(\)\<\>\@\,\;\:\\\"\/\[\]\?\=]+)\s*\=(.*)$/){ + if (lc $attribute eq lc $1) { + $val = $2; + $val =~ s/^\s+//; + if ($val =~ /\"(.*)\"/) { + $val = $1; + $val =~ s/\\(.)/$1/g; + } + return ($val, 1); + } + } + } + return ('', 0); +} + +sub get_charset { +# ($val, $present) = &get_charset (@header) +# Get the content-type: charset parameter from the header. Return +# ('', 1) if text/plain but no charset field is present. + my (@header) = @_; + my (@mime_fields); + my ($val, $present); + my ($type_base, @type_params); + + ($val, $present) = &lookup_val ('mime-version', @header); + if (!$present) { return ('', 0); } + ($val, $present) = &lookup_val ('content-type', @header); + if (!$present) { return ('', 0); } + ($type_base, @type_params) = &split_mime_params ($val); + if (lc $type_base eq 'text/plain') { + ($val, $present) = &get_mime_param ('charset', @type_params); + if ($present) { + return ($val, 1); + } else { + return ('', 1); + } + } + return ('', 0); +} + +sub encode_base64 { +# $encoded = &encode_base64 ($raw) +# Convert raw binary string into MIME base64 encoding (RFC 1521). + my ($raw) = @_; + my ($encoded); + + $encoded = pack ("u", $raw); + chop $encoded; + $encoded =~ s/^.//; + $encoded =~ tr/\`\!-\_/A-Za-z0-9\+\//; + if ((length $raw) % 3 == 1) { $encoded =~ s/..$/\=\=/; } + elsif ((length $raw) % 3 == 2) { $encoded =~ s/.$/\=/; } + return $encoded; +} + +sub encode_qp_byte { +# $encoded = &encode_qp_byte ($char) + return '='.uc sprintf ('%02x', unpack ('C', $_[0])); +} + +sub encode_qp { +# $encoded = &encode_qp ($line, $type) +# Convert text line into MIME quoted-printable encoding (RFC 1521). Result +# may be multiple lines. Argument must be one line. +# $type argument should be one of the following: +# 'sign' - quote "From ", tabs +# otherwise minimal encoding needed to conform to spec. + my ($line, $type) = @_; + my ($before, $after); + + chop $line; + if ($type eq 'sign') { + $line =~ s/([^ -\<\>-\~])/&encode_qp_byte($1)/eg; + $line =~ s/^From /\=46rom /; + $line =~ s/^\.$/\=2E/; + } else { + $line =~ s/([^\t -\<\>-\~])/&encode_qp_byte($1)/eg; + } + $line =~ s/([ \t])$/&encode_qp_byte($1)/e; + $before = ''; + while (length $line > 76) { + $after = substr ($line, 75); + $line = substr ($line, 0, 75); + if ($line =~ /(\=.|\=)$/) { + $after = substr ($line, 75 - length $1). $after; + $line = substr ($line, 0, 75 - length $1); + } + $line = $line."\=\n"; + $before .= $line; + $line = $after; + } + return $before.$line."\n"; +} + +sub purify_mime { +# $new_body = &purify_mime ($body, $type) +# Make the message in ($deliver_headers, $body) MIME compliant. +# Modify @deliver_headers if necessary (charset promotion, demotion). +# +# General outline: first determine whether or not to qp encode the +# body. If we decide to, then qp encode it. +# Here are reasons why we might decide to qp encode: +# +# line contains characters other than '\t', '0'-'~' (also promote charset) +# line begins with "From " ('sign' $type and pgpmime only) +# line is "." ('sign' $type only) + + my ($body, $type) = @_; + my ($catch_from, $line); + my ($non_ascii, $ctrl, $other); + my ($charset, $charset_present); + my ($new_body); + my ($val, $present); + my ($mv_val, $mv_present); + my ($ct_val, $ct_present); + my ($cte_val, $cte_present); + my ($type_base, @type_params); + my (@mime_fields); + + # Check out the status of the existing MIME headers, if any + $ct_present = 0; + $cte_present = 0; + ($mv_val, $mv_present) = &lookup_val ("MIME-Version", @deliver_headers); + if ($mv_present) { + ($ct_val, $ct_present) = &lookup_val("Content-Type", @deliver_headers); + ($cte_val, $cte_present) = &lookup_val ("Content-Transfer-Encoding", + @deliver_headers); + if ($cte_present && (lc $cte_val eq 'quoted-printable' + || lc $cte_val eq 'base64')) { + # If it's already qp or base64 encoded, return. + # Note: We could still have problems with "From" wedging and + # other heebie-jeebies, but we're trusting the mailer to have + # done a good job. + return $body; + } + } + # Now, we know that it's one of the "raw" encodings (7bit, 8bit, binary). + + $body = &prepare_for_n_passes ($body, 2); + (@mime_fields) = &get_mime_fields (@deliver_headers); + $catch_from = ($config{'pgpmime'} || $#mime_fields >= 0); + $non_ascii = 0; + $ctrl = 0; + $other = 0; + &open_body ($body); + if ($type eq 'sign') { + while (defined ($line = &get_line_body ($body))) { + chop $line; + $non_ascii ||= ($line =~ /[\200-\377]/); + $ctrl ||= ($line =~ /[^\t -\377]/); + $other ||= ($line eq '.' + || $catch_from && $line =~ /^From /); + } + } else { + while (defined ($line = &get_line_body ($body))) { + chop $line; + $non_ascii ||= ($line =~ /[\200-\377]/); + $ctrl ||= ($line =~ /[^\t -\377]/); + } + } + &close_body ($body); + &pdv ("purify_mime: \$non\_ascii\=$non_ascii \$ctrl\=$ctrl \$other\=$other\n"); + + if ($ct_present) { + ($type_base, @type_params) = &split_mime_params ($ct_val); + } + if (!$ct_present || lc $type_base eq 'text/plain') { + if ($ct_present) { + ($val, $present) = &get_mime_param ('charset', @type_params); + if ($present) { + $charset = $val; + } else { + $charset = 'us-ascii'; + } + } else { + $charset = 'us-ascii'; + } + &pdv ("purify_mime: \$charset\=$charset \$ct\_present\=$ct_present \$mv\_present\=$mv_present\n"); + if (lc $charset eq 'us-ascii' && $non_ascii) { + if (!$mv_present) { + push (@deliver_headers, 'MIME-Version: 1.0'."\n"); + $mv_present = 1; + } + @deliver_headers = + &replace_field ('Content-Type: text/plain; charset=' + .$config{'charset'}."\n", + @deliver_headers); + } elsif (($charset =~ /^iso-8859-\d$/i || $charset =~ /^koi8-r$/i) && !$non_ascii) { + # Should we detect other charsets which are supersets of us-ascii? + if (!$mv_present) { + push (@deliver_headers, 'MIME-Version: 1.0'."\n"); + $mv_present = 1; + } + @deliver_headers = + &replace_field ('Content-Type: text/plain'."\n", + @deliver_headers); + + } + } + # must deal with existing cte, charset, etc. + if ((($non_ascii || $ctrl) && (!$cte_present || lc $cte_val ne '8bit')) || $other) { + # Do the QP + &pdv ("Doing QP encoding!\n"); + if (!$mv_present) { + push (@deliver_headers, 'MIME-Version: 1.0'."\n"); + } + @deliver_headers = + &replace_field ('Content-Transfer-Encoding: quoted-printable'."\n", + @deliver_headers); + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || die "Couldn't open file: $!"; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print NEW &encode_qp ($line, $type); + } + &close_body ($body); + close (NEW); + $body = $new_body; + } + return $body; +} + +sub canonicalize_line_enc { +# $canonical_line = &canonicalize_line ($line) +# Perform canonicalization according to PGP/MIME spec. Can handle "lines" +# with multiple newlines. +# +# Spec is still in flux. +# +# This version of the routine generates newlines, which is the correct +# format to give to PGP when using the "-t" option, at least on Unix +# systems. If you are porting premail to a system with CRLF line ends, +# then the /\n/ should probably become /\r\n/. + my ($line) = @_; + + $line =~ s/\r?\n/\n/sg; + return $line; +} + +sub canonicalize_line { +# $canonical_line = &canonicalize_line ($line) +# Perform canonicalization according to PGP/MIME spec. Can handle "lines" +# with multiple CR's. +# +# Spec is still in flux. + my ($line) = @_; + + $line =~ s/\r?\n/\r\n/sg; + return $line; +} + +sub canonicalize_line_moss { +# $canonical_line = &canonicalize_line_moss ($line) +# Perform canonicalization according to MOSS spec. Can handle "lines" +# with multiple CR's. +# +# Consistent with RFC 1848. + my ($line) = @_; + + $line =~ s/\r?\n/\r\n/sg; + return $line; +} + +sub mknonbin { +# $newfile = &mknonbin ($infile) +# Convert MIME object in $infile to non-binary representation, store in +# $newfile, or just return $infile if it's already non-binary. Decrement +# reference count of $infile if the conversion does happen. + my ($infile) = @_; + my ($newfile); + my ($buf, $inbuf, $outbuf, $blocksize, $state); + my (@sepstack); + my ($n, $i, $nlsize, $eof, $eob, $more); + my (@header, @mime_fields); + my ($val, $present, $param_val); + my ($type_base, @type_params); + + open (MNBIN, $infile) || die "Couldn't open file: $!"; + $newfile = ''; + @sepstack = (); + $blocksize = 1024; + $state = 0; # 0 = waiting for header + # 1 = inside non-binary part + # 2 = inside binary part + # 3 = just before initial newline in binary part + $eof = 0; + sysread (MNBIN, $buf, $blocksize); + while (!$eof || $buf ne '') { +# print STDERR 'sepstack: '.join (', ', @sepstack).", "; +# print STDERR ("state $state; buf = ".&encode_qp (substr ($buf, 0, 20)."\n")); + $n = length $buf; + if (!$eof && ($more || $n < $blocksize)) { + $n = sysread (MNBIN, $inbuf, $blocksize); +# print "read $n\n"; + if ($n == 0) { $eof = 1; } + $buf .= $inbuf; + } + $more = 0; + if ($state == 0) { + # try to get header + if ($buf =~ /^\r?\n/s) { + $i = 0; + $nlsize = 0; + } else { + $i = index ($buf, "\n\n"); + if ($i >= 0) { + $nlsize = 1; + } else { + $i = index ($buf, "\r\n\r\n"); + if ($i >= 0) { + $nlsize = 2; + } + } + } + if ($i >= 0) { + # found the header, let's process + @header = &split_header (substr ($buf, 0, $i + $nlsize)); + $buf = substr ($buf, $i + $nlsize); + @mime_fields = &get_mime_fields (@header); + $state = 1; # if not binary - override later if binary + # find out if it's a multipart + ($val, $present) = &lookup_val ('Content-Type', @header); + if ($present) { + ($type_base, @type_params) = &split_mime_params ($val); + if ($type_base =~ /^multipart\//i) { + ($val, $present) = &get_mime_param ('boundary', + @type_params); + if ($present) { + push (@sepstack, $val); +# print 'sepstack: '.join (', ', @sepstack)."\n"; + } + } + } + # find out if it's binary + ($val, $present) = &lookup_val ('Content-Transfer-Encoding', + @header); + if ($present) { + ($type_base, @type_params) = &split_mime_params ($val); + if (lc $type_base eq 'binary') { + $state = 3; + @header = &replace_field + ('Content-Transfer-Encoding: base64'."\n", + @header); + } + } + if ($#sepstack < 0 && $state == 1) { + return $infile; + } elsif ($newfile eq '') { + $newfile = &tmp_filename (); +# print STDERR "newfile = $newfile\n"; + open (MNBOUT, '>'.$newfile) || die "Couldn't open file: $!"; + } + print MNBOUT (join ('', @header)); + } elsif ($eof) { + # didn't find a header - just dump to output + if ($#sepstack < 0) { return $infile; } + print MNBOUT $buf; + $buf = ''; + } else { + $more = 1; + } + } else { + # in body - first, check for boundary + if ($#sepstack < 0) { + $eob = $eof; + $outbuf = $buf; + $buf = ''; + } else { + $n = 6 + length $sepstack[$#sepstack]; + $i = index ($buf, "\r\n".'--'.$sepstack[$#sepstack]."\r\n"); + if ($i < 0) { + $n = 4 + length $sepstack[$#sepstack]; + $i = index ($buf, "\n".'--'.$sepstack[$#sepstack]."\n"); + } + if ($i >= 0) { + $eob = 1; + if ($i == 0) { + print MNBOUT ("\n".'--'.$sepstack[$#sepstack]."\n"); + $buf = substr ($buf, $n); + $outbuf = ''; + $state = 0; + } else { + $outbuf = substr ($buf, 0, $i); + $buf = substr ($buf, $i); + } + } else { + $n = 8 + length $sepstack[$#sepstack]; + $i = index ($buf, "\r\n".'--'.$sepstack[$#sepstack].'--' + ."\r\n"); + if ($i < 0) { + $n = 6 + length $sepstack[$#sepstack]; + $i = index ($buf, "\n".'--'.$sepstack[$#sepstack].'--' + ."\n"); + } + if ($i >= 0) { + $eob = 1; + if ($i == 0) { + print MNBOUT ("\n".'--'.$sepstack[$#sepstack] + .'--'."\n"); + $buf = substr ($buf, $n); + $outbuf = ''; + pop (@sepstack); + $state = 1; + } else { + $outbuf = substr ($buf, 0, $i); + $buf = substr ($buf, $i); + } + } else { + $n = (length $buf); + if (!$eof) { $n -= 8 + length $sepstack[$#sepstack]; } + $outbuf = substr ($buf, 0, $n); + $buf = substr ($buf, $n); + } + } + } + if ($outbuf ne '' && $state == 1) { + print MNBOUT $outbuf; + $outbuf = ''; + } elsif ($outbuf ne '' && $state == 2) { + if ($eob || length $outbuf >= 15 * 3) { + print MNBOUT (&encode_base64 (substr ($outbuf, 0, 15 * 3)) + ."\n"); + $outbuf = substr ($outbuf, 15 * 3); + } + } elsif ($outbuf ne '' && $state == 3) { + if ($outbuf =~ /^\n/s) { + $outbuf = substr ($outbuf, 1); + print MNBOUT "\n"; + } elsif ($outbuf =~ /^\r\n/s) { + $outbuf = substr ($outbuf, 2); + print MNBOUT "\n"; + } + $state = 2; + } + $buf = $outbuf.$buf; + } # if ($state == 0) + } # while (!($eof && length $buf == 0)) + close (MNBIN); + &refcnt_bump ($infile, -1); + close (MNBOUT); + return $newfile; +} + +sub split_header { +# @header = &split_header ($header) +# Convert header from a single string into premail dict style (i.e. one +# key: value pair per list entry). +# +# Canonicalize line ends to LF. + my ($header) = @_; + my (@header); + + @header = (); + foreach $line (split (/\r?\n/, $header)) { + if ($line =~ /^\S/) { + push (@header, $line."\n"); + } elsif ($line =~ /^\s/) { + push (@header, pop (@header).$line."\n"); + } + } + return (@header); +} + +########################################## +# special commands + +sub usage { + print "Usage:\n"; + print " premail [-options]\n"; + print " Similar options as sendmail\n"; + print "\n"; + print " premail -decode \n"; + print " Decode the message (stdin if omitted)\n"; + print " premail -decode -body \n"; + print " Decode the message body (stdin if omitted)\n"; + print "\n"; + print " premail -makenym nym\@server real\@email.address\n"; + print " Create an anonymous account\n"; + print "\n"; + print " premail -login\n"; + print " premail -logout\n"; + print " Log in or log out secrets file\n"; + print " premail -setpass\n"; + print " Set passphrase for secrets file\n"; + print "\n"; + print " premail -ripemkey\n"; + print " Generate S/MIME key\n"; + print "\n"; + print "Please see http://www.c2.net/~raph/premail/ for more info.\n"; + exit 0; +} + +sub get_remailer_pubring { + my ($pubring, $pubring_fn); + + if (&open_web ($config{'pubring-pgp'})) { + $/ = ''; + $pubring = ; + $/ = "\n"; + close (WWW); + if ($pubring ne '') { + $pubring_fn = &tilde_expand_mkdir ($config{'pubring'}); + open (PUB, '>'.$pubring_fn) || die "Couldn't open file: $!"; + print PUB $pubring; + close (PUB); + } + } +} + +sub get_mix_keys { + my ($mix); + + if ($got_mix_keys) { return; } + $got_mix_keys = 1; + $mix = &tilde_expand ($config{'mixmaster'}); + if (!open (MIX, "$mix -P|")) { + return; + } + $mix_dir = ; + $mix_type2_list = ; + close (MIX); + if (!defined $mix_dir || $mix_dir eq '') { + &error ( + "Cannot get information from mixmaster - need version 2.0.2 or better\n"); + } + chop $mix_dir; + chop $mix_type2_list; + if (&is_stale ($mix_dir.'/'.$mix_type2_list, 3600) + && $config{'type2-list-url'}) { + &getfile_from_web_html ($mix_dir.'/'.$mix_type2_list, + $config{'type2-list-url'}); + &getfile_from_web_html ($mix_dir.'/pubring.mix', + $config{'pubring-mix-url'}); + } +} + +########################################## +# the decode pipeline + +sub decode { + my (@args) = @_; + my ($key, $val); + my (@new_headers); + my ($msg_body, $line); + my ($body_only); + + $error_mode = 'd'; + &set_configs (); + $body_only = 0; + # Set up in preparation for &open_input + if ($#args >= 0 && $args[0] eq '-body') { + $body_only = 1; + shift @args; + } + if ($#args >= 0) { + $edit = 1; + $editfile = $args[0]; + } else { + $dashoi = 1; + } + + &open_input (); + $line = &get_header ('-', 1) unless $body_only; + if ($line) { + # Decode a whole mailbox. + print $line; + $state = 0; + $msg_body = &tmp_filename (); + open (MSG, '>'.$msg_body) || die "Couldn't open file: $!"; + while (defined ($line = &get_line ())) { + if ($line =~ /^From / && $state == 1) { + close (MSG); + &decode_msg ($msg_body); + print "\n"; + print $line; + push (@open_tmpfiles, $msg_body); + $tmpfile_refcnt{$msg_body} = 1; + open (MSG, '>'.$msg_body) || die "Couldn't open file: $!"; + $state = 0; + } elsif ($state == 0 && $line eq "\n") { + $state = 1; + } else { + if ($state == 1) { print MSG "\n"; } + $state = ($line eq "\n"); + print MSG $line unless $state; + } + } + close (MSG); + &decode_msg ($msg_body); + print "\n"; + } else { + foreach $field (@in_headers) { + ($key, $val) = &parse_field ($field); + if ($key =~ /^x\-premail\-auth$/i) { + push (@new_headers, "X\-Attempted\-Auth\-Forgery: $val\n"); + } elsif ($key =~ /^x\-attempted\-auth\-forgery$/i) { + push (@new_headers, 'X\-Meta-'.$field); + } else { + push (@new_headers, $field); + } + } + @deliver_headers = @new_headers; + &decode_body ($in_body, '', 0); + } +# &error ("error!\n"); + if ($move_fn) { + close (MOVE_OUT); + rename ($move_work_fn, $move_fn); + } + &delete_open_tmpfiles (); + exit 0; +} + +use vars qw($SAVE_BODY); + +sub decode_msg { +# &decode_msg ($msg) +# This is possibly the ugliest function in all of premail. Most of it is +# taken up with working around the elaborate internal economy I've designed +# for the rest of the program. Plus, it creates two temporary files. But +# hey, it works. + my ($msg) = @_; + my ($line); + my ($key, $val); + my (@new_headers); + my ($save_in_body); + my ($msg_body, $new_msg, $save_select); + + if ($msg ne '-') { + &open_body ($msg); + open (SAVE_BODY, "<&BODY") || die "Can't open a save file: $!"; + } + &get_header ($msg); + $msg_body = &tmp_filename (); + open (MSG_BODY, '>'.$msg_body) || die "Can't open a message file: $!"; + while (defined ($line = &get_line_body ($msg))) { + print MSG_BODY $line; + } + close (MSG_BODY); + foreach $field (@in_headers) { + ($key, $val) = &parse_field ($field); + if ($key =~ /^x\-premail\-auth$/i) { + push (@new_headers, "X\-Attempted\-Auth\-Forgery: $val\n"); + } elsif ($key =~ /^x\-attempted\-auth\-forgery$/i) { + push (@new_headers, 'X-Meta-'.$field); + } else { + push (@new_headers, $field); + } + } + @deliver_headers = @new_headers; + $new_msg = &tmp_filename (); + open (NEW_MSG, '>'.$new_msg) || die "Couldn't open file: $!"; + $save_select = select NEW_MSG; + select NEW_MSG; + &decode_body ($msg_body, '', 0); + close NEW_MSG; + select $save_select; + &open_body ($new_msg); + while (defined ($line = &get_line_body ($new_msg))) { + if ($line !~ /\n$/s) { $line .= "\n"; } + $line =~ s/^From /\>From /; # re-wedge + print $line; + } + &close_body ($new_msg); + if ($msg ne '-') { + &close_body ($msg); + open (BODY, "<&SAVE_BODY") || die "Couldn't open file: $!"; + } +} + +sub decode_body { +# &decode_body ($body, $nym, $nym_num) +# Decode (@deliver_headers, $header_sep, $body) (recursively if +# necessary), and send to standard out. +# +# I am unhappy with the "body" structure, as it writes plaintext to a +# temp file. However, I'm not sure whether to change it or not. + my ($body, $nym, $nym_num) = @_; + my (@window, $state, $pgp_body, $new_body, $err); + my (@userlist, @typelist, $encrypted); + my (@mime_fields, $absorb); + my ($ct_val, $ct_present); + my ($type_base, @type_params); + my ($param_val, $present); + my ($protocol, $boundary, $multipart); + my ($body_open, $pass); + my ($doublestar, $num_nym2); + + $encrypted = 0; + @mime_fields = &get_mime_fields (@deliver_headers); + ($ct_val, $ct_present) = &lookup_val ("Content-Type", @mime_fields); + if ($ct_present) { + ($type_base, @type_params) = &split_mime_params ($ct_val); +# print $type_base.'; '.join ('; ', @type_params)."\n"; + if (lc $type_base eq 'application/pgp' + || lc $type_base eq 'application/x-pgp') { + # Deal with obsolete application/pgp formats + ($param_val, $present) = &get_mime_param ('format', @type_params); + $absorb = ($present && lc $param_val eq 'mime'); + } elsif (lc $type_base eq 'multipart/encrypted') { + ($protocol, $present) = &get_mime_param ('protocol', + @type_params); + $protocol = lc $protocol; + ($boundary, $present) = &get_mime_param ('boundary', @type_params); + $encrypted = 1; + $absorb = 1; + $multipart = 1; + } elsif (lc $type_base eq 'multipart/signed') { + ($protocol, $present) = &get_mime_param ('protocol', + @type_params); + $protocol = lc $protocol; + ($boundary, $present) = &get_mime_param ('boundary', @type_params); + $absorb = 1; + $multipart = 1; + } elsif (lc $type_base eq 'application/x-pkcs7-mime' + || lc $type_base eq 'application/pkcs7-mime') { + &decode_smime ($body); + return; + } + } + + &open_body ($body); + @window = (); + $body_open = 0; + $doublestar = 0; + $state = 0; # 0 = undecided, 1 = PGP, 2 = non-PGP + while (defined ($line = &get_line_body ($body))) { +# print STDERR $state.$line; + if ($state == 0 && ($line eq '-----BEGIN PGP MESSAGE-----'."\n" + || $line eq '-----BEGIN PGP SIGNED MESSAGE-----'."\n" + || $multipart)) { + if ($line eq '-----BEGIN PGP MESSAGE-----'."\n") { + $encrypted = 1; + } + $pgp_body = &tmp_filename (); + open (DEC, '>'.$pgp_body) || die "Couldn't open file: $!"; + $body_open = 1; + foreach $l (@window) { + print DEC $l; + } + @window = (); + print DEC $line; + $state = 1; + } elsif ($state == 0) { + $doublestar ||= ($line eq "\*\*\n"); + push (@window, $line); + if ($#window + 1 == 20) { + &fix_decode_header (); + foreach $l (@deliver_headers) { + print $l; + } + print $header_sep; + foreach $l (@window) { + print $l; + } + @window = (); + $state = 2; + } + } elsif ($state == 1) { + print DEC $line; + } elsif ($state == 2) { + print $line; + } + } + &close_body ($body); + if ($body_open) { close (DEC); } + if ($state == 0) { + &fix_decode_header (); + foreach $line (@deliver_headers) { + print $line; + } + print $header_sep; + foreach $line (@window) { + print $line; + } + return; + } elsif ($state == 2) { + return; + } + # Now we know it's a PGP message, living in $body. + if ($encrypted && + (!$multipart || $protocol eq 'application/pgp-encrypted')) { + &load_secrets (); + ($PUBRING, $SECRING) = &makebigrings unless ($PUBRING || $SECRING); + @typelist = @userlist = (); + if (%pgpring) { + push @typelist, 'hiddenpgp'; + push @userlist, ''; + } + if (!$doublestar) { + foreach $user (keys %pgppass) { + push (@typelist, 'user'); + push (@userlist, $user); + } + } + } else { + @typelist = ('sign'); + @userlist = (''); + } + if ($encrypted && !$multipart) { + # Try the nyms as well + if ($nym) { + @typelist = ('nym'); + @userlist = ($nym); + } else { + foreach $nym2 (@nym_list) { + $num_nym2 = &nym_numpasses ($nym2); + if ($num_nym2 == 1 && !$doublestar + || $num_nym2 > 1 && $doublestar) { + push (@typelist, 'nym'); + push (@userlist, $nym2); + } + } + } + } + for $i (0..$#userlist) { + # Try decrypting using $pgppass{$user} + if (!$nym && $typelist[$i] eq 'nym') { + $nym_num = &nym_numpasses ($userlist[$i]) - 1; + } + $pass = &user_pass ($typelist[$i], $userlist[$i], $nym_num); +# print "$typelist[$i] $userlist[$i] $nym_num $pass\n"; + $pgp_body = &prepare_for_n_passes ($pgp_body, 2); + if ($multipart) { + ($new_body, $err) = &decode_multipart ($pgp_body, $pass, + $boundary, $protocol); + } else { + ($new_body, $err) = &pgp_decrypt ($pgp_body, $pass); + } + if ($new_body) { + if (!$encrypted && $err =~ /(^|\n)\007?([^\n]* not found)/si + || $err =~ /(^|\n)([^\n]* don\'t have MOSS installed)/) { + # Note: 1st match expression extremely specific to PGP 2.6.2 + &premail_auth ($2); + &delete_tmpfile ($new_body); + } else { + if ($typelist[$i] eq 'nym') { + # Note: here we break the premail_auth abstraction + if ($nym && $premail_auth[$#premail_auth] =~ + /^partially decrypted/) { + pop (@premail_auth); + } + if ($nym_num && $userlist[$i] =~ /^(\d+),(.*)=(.*)$/) { + &premail_auth + ("partially decrypted nym $3\@$2, number $1" + ." with $nym_num steps remaining"); + } elsif (!$nym_num && $userlist[$i] =~ + /^(\d+),(.*)=(.*)$/) { + &premail_auth + ("decrypted nym $3\@$2, number $1"); + } + } elsif ($typelist[$i] eq 'user') { + &premail_auth ("decrypted for $userlist[$i]"); + } elsif ($typelist[$i] eq 'hiddenpgp') { + if ($err =~ /^Key for user ID:\s*(\S+.*)$/m) { + &premail_auth ("decrypted for $1"); + } elsif ($err =~ + /^[0-9]+-bit key, Key ID ([0-9A-F]{8})/m) { + &premail_auth ("decrypted for key ID $1"); + } else { + print STDERR $err; + &premail_auth ("decrypted for unknown user"); + } + } + if ($err =~ /(^|\n)(\w+ signature[^\n]*)\n/si + || $err =~ /(^|\n)\007?([^\n]* not found)/si) { + # Note: match expression extremely specific to PGP 2.6.2 + &premail_auth ($2); + } + &delete_tmpfile ($pgp_body); + &extract_mime_fields (); + $absorb ||= ($typelist[$i] eq 'nym' && $nym_num == 0); + if ($absorb) { + push (@deliver_headers, "MIME-Version: 1.0\n") + unless $typelist[$i] eq 'nym'; + $new_body = &absorb_mime_headers ($new_body); + } + if ($typelist[$i] eq 'nym') { + $nym_num--; + if ($nym_num >= 0) { $nym = $userlist[$i]; } + else { $nym = ''; $nym_num = 0; } + } + &decode_body ($new_body, $nym, $nym_num); + return; + } + } + } + &decode_nothing ($pgp_body); +} + +sub decode_nothing { +# &decode_nothing ($body) +# +# All attempts to decrypt failed; just output the file. + my ($body) = @_; + + &fix_decode_header (); + foreach $line (@deliver_headers) { + print $line; + } + print $header_sep; + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { + print $line; + } + &close_body ($body); +} + +sub premail_auth { + + push (@premail_auth, @_); + &pdv ("premail_auth: $_[0]\n"); +} + +sub fix_decode_header { +# Actually adds premail-auth to the header, and also fixes up the +# $header_sep variable, if that needs to be done. + my ($msg); + + if ($#premail_auth >= 0) { + if ($gist) { + $msg = join ('; ', @premail_auth); + print STDERR "200 $msg\n"; + } else { + $msg = &wordwrap ('X-Premail-Auth: ' + .join ('; ', @premail_auth), 71, ' '); + push (@deliver_headers, $msg); + } + if ($header_sep eq '' && $#deliver_headers >= 0) { + $header_sep = "\n"; + } + } + @premail_auth = (); +} + +sub user_pass { +# $pass = &user_pass ($type, $user, $nym_num) +# Extract the password, if there is one. +# +# The handling of nyms is a bit oversimplified. This only works on +# reply blocks without encrypt-key. In the latter case, we would want +# to get the last encrypt-key in the chain, if there was one. That's +# a tricky regular expression, at best, especially if we allow chains +# to have arbitrary other stuff in them, such as latency. + my ($type, $user, $nym_num) = @_; + my (@pass_list); + +# print "$type $user $nym_num\n"; + if ($type eq 'sign' || $type eq 'hiddenpgp') { + return ''; + } elsif ($type eq 'user') { + return $pgppass{$user}; + } elsif ($type eq 'nym') { + @pass_list = &nym_passlist ($user); + return $pass_list[$nym_num]; + } + return ''; +} + +sub nym_type { + my ($nym) = @_; + &get_remailers; + if ($nym =~ /^\d+,(\w+)=/) { + local ($_) = ($options{$1}); + /\balpha\b/ && return 'alpha'; + /\bnewnym\b/ && return 'newnym'; + } + return undef; +} + +sub nym_passlist { +# @pass_list = &nym_passlist ($nym) +# Given a nym, return the list of passphrases, in order of the chain. + my ($nym) = @_; + my (@pass_list); + my $nymid = (split /,/, $nym)[1]; + + if ($nym{$nym} =~ /(\^|^)pass\=([^\^]*)(\^|$)/ + || $pgpring{$nymid}) { + if ($pgpring{$nymid}) { + @pass_list = (''); + } + else { + @pass_list = ($2); + } + if ($nym{$nym} =~ /(\^|^)chain\=([^\^]*)(\^|$)/) { + foreach $hop (split (/\;/, $2)) { + if ($hop =~ /\.encrypt\-key\:\s*([^\s\.]+)(\.|$)/i) { + push (@pass_list, $1); + } + } + } + } + return @pass_list; +} + +sub nym_numpasses { + my ($nym) = @_; + my (@pass_list); + + @pass_list = &nym_passlist ($nym); + return $#pass_list + 1; +} + +sub decode_multipart { +# ($new_body, $err) = &decode_multipart ($body, $pass, $boundary, $protocol) +# +# Decode a message in MIME multipart format. On success, return a +# $new_body, with the PGP-style return string in $err. +# +# One point: with the current structure, it will parse the multiparts +# over again for each attempted passphrase. This is not a serious +# performance problem now, but would be if the type-3 nymserver ever got +# implemented. + my ($body, $pass, $boundary, $protocol) = @_; + my ($part, $body_open); + my (@body); + my (@window); + my ($state, $cte, $canon); + my ($new_body, $errfile, $new_err); + + &pdv ("decode_multipart $body $boundary $protocol\n"); + $part = 0; + $body_open = 0; + @window = (); + &open_body ($body); + while (defined ($line = &get_line_body ($body))) { +# print "$part$line"; + if ($body_open && ($line eq '--'.$boundary."\n" + || $line eq '--'.$boundary.'--'."\n")) { + # Handle last line fragment (usually empty) + $frag = shift @window; + $frag =~ s/\r?\n$//; + print NEW $frag; + close (NEW); + $body_open = 0; + } + if ($line eq '--'.$boundary."\n") { + $part++; + $state = 0; + $cte = ''; + if ($part == 1 && ($protocol eq 'application/moss-signature' + || $protocol eq 'application/pgp-signature' + || $protocol eq 'application/x-pkcs7-signature' + || $protocol eq 'application/pkcs7-signature')){ + $body[$part] = &tmp_filename (); + open (NEW, '>'.$body[$part]) || die "Couldn't open file: $!"; + $body_open = 1; + $state = 1; + $canon = ($protocol eq 'application/pgp-signature' + || $protocol eq 'application/x-pkcs7-signature' + || $protocol eq 'application/pkcs7-signature'); + } + } elsif ($state == 0 && $line eq "\n") { + if ($protocol ne 'application/pgp-encrypted' && $part == 1 + || $part == 2) { + $body[$part] = &tmp_filename (); + if ($cte eq '' || &mossbin('mossdecode', 1) eq '') { + open (NEW, '>'.$body[$part]) || + die "Couldn't open file: $!"; + } elsif ($cte eq 'quoted-printable') { + open (NEW, '|'.&mossbin ('mossdecode') + .' -qp > '.$body[$part]) || + die "Couldn't open file: $!"; + } elsif ($cte eq 'base64') { + open (NEW, '|'.&mossbin ('mossdecode') + .' -b64 > '.$body[$part]) || + die "Couldn't open file: $!"; + } else { + &error ("Unknown Content-Transfer-Encoding: $cte\n"); + } + $canon = ($part == 1 + && $protocol eq 'application/pgp-signature'); + $body_open = 1; + } + $state = 1; + } elsif ($state == 0 && $line =~ + /^content\-transfer\-encoding\:\s+([\w\-]+)/i) { + $cte = lc $1; + } elsif ($body_open && $line eq '--'.$boundary.'--'."\n") { + last; + } elsif ($body_open) { + print NEW @window; + if ($canon) { + @window = (&canonicalize_line ($line)); + } else { + @window = ($line); + } + } + } + if ($body_open) { close (NEW); } + &close_body ($body); + if ($part != 2 || $body_open) { + return ('', '') + } + if ($protocol eq 'application/pgp-encrypted') { + ($new_body, $err) = &pgp_decrypt ($body[2], $pass); + $new_body = &mknonbin ($new_body) if $new_body; + } elsif ($protocol eq 'application/pgp-signature') { + ($err) = &pgp_verify ($body[1], $body[2]); + &delete_tmpfile ($body[2]); + $new_body = $body[1]; + } elsif ($protocol eq 'application/moss-keys') { + $new_body = &tmp_filename (); + $errfile = &tmp_filename; + system &mossbin('decrypt').' header-in '.$body[1].' data-in '.$body[2] + .' data-out '.$new_body.' > '.$errfile.' 2>&1'; + if ($?) { + &delete_tmpfile ($new_body); + $new_body = ''; + } + $err = &read_and_delete ($errfile); + &delete_tmpfile ($body[1]); + &delete_tmpfile ($body[2]); + } elsif ($protocol eq 'application/moss-signature') { + $errfile = &tmp_filename; + if (&mossbin ('mossdecode', 1) eq '') { + $new_body = $body[1]; + &delete_tmpfile ($body[2]); + $err = "Can't check MOSS signature; don't have MOSS installed\n"; + } else { + system &mossbin('canon').' < '.$body[1].' | '.&mossbin('verify') + .' header-in '.$body[2].' > '.$errfile.' 2>&1'; + $new_body = $body[1]; + $err = &read_and_delete ($errfile); + &pdv ($err); + &delete_tmpfile ($body[2]); + if ($err =~ /(^|\n)Originator user with (.*) is (.*) as follows/s) { + $new_err = "$3 $2"; + if ($err =~ /(^|\n)Signature has been verified/s) { + $err = "Good signature from $new_err\n"; + } else { + $err = "Bad signature from $new_err\n"; + } + } + &pdv ($err); + } + } elsif ($protocol eq 'application/x-pkcs7-signature' + || $protocol eq 'application/pkcs7-signature') { + &pdv ($body[1].":\n"); + &pdv (`od -c $body[1]`); + &pdv ($body[2].":\n"); + &pdv (`cat $body[2]`); + ($err) = &verify_smime ($body[1], $body[2]); + &delete_tmpfile ($body[2]); + $new_body = $body[1]; + } + return ($new_body, $err); +} + +sub absorb_mime_headers { +# $new_body = &absorb_mime_headers ($body) +# Absorb the MIME headers from the MIME object in $body to @deliver_headers. + my ($body) = @_; + my ($new_body); + my (@header, $line, $state); + my ($key, $val); + + $| = 1; + $new_body = &tmp_filename (); + open (NEW, '>'.$new_body) || die "Couldn't open file: $!"; + &open_body ($body); + $state = 0; + while (defined ($line = &get_line_body ($body))) { + # Adapted from get_header + $line =~ s/\r\n/\n/; + @in_headers = (); # What the hell is this? + if ($state == 0 && $line =~ /^([!-9\;-\177]+)\:\s*(.*)$/) { + push (@header, $line); + } elsif ($state == 0 && $#header >= 0 && $line =~ /^\s(.*)\n/) { + $line = pop (@header) . $line; + push (@header, $line); + } elsif ($state == 0 && ($line eq '' || $line eq "\n")) { + $state = 1; + } else { + print NEW $line; + $state = 1; + } + } + foreach $field (@header) { + ($key, $val) = &parse_field ($field); + if (lc $key eq 'received') { + push (@deliver_headers, $field); + } else { + @deliver_headers = &replace_field ($field, @deliver_headers); + } + } + &close_body ($body); + close (NEW); + return $new_body; +} + +sub decode_smime { +# &decode_smime ($body) +# Decode (@deliver_headers, $header_sep, $body) (recursively if +# necessary), and send to standard out. We now know it's an S/MIME message. + my ($body) = @_; + my ($cte, $cte_present); + my ($new_body, $errfile, $err); + my ($invoc); + + &load_secrets (); + if (!defined $ripemuser) { + &error ("Must specify \$ripempass{''} = ''; in secrets file\n"); + } + ($cte, $cte_present) = &lookup_val ("Content-Transfer-Encoding", + @deliver_headers); + if (!$cte_present || lc $cte ne 'base64') { + &error ("Can only handle base64 c-t-e in S/MIME messages\n"); + } + $new_body = &tmp_filename (); + $invoc = &tilde_expand ($config{'ripem'}); + $invoc .= ' -d -B -M pkcs -k -'; + if (defined $ripemuser) { $invoc .= ' -u '.$ripemuser; } + $body = &force_file_body ($body); + $invoc .= ' -i '.$body; + $invoc .= ' -o '.$new_body; + $errfile = &tmp_filename (); + $invoc .= ' > '.$errfile.' 2>&1'; + &pdv ("Invoking RIPEM as $invoc\n"); + if (!open (RIPEM, "|$invoc")) { + &error ("Error invoking RIPEM\n"); + } + print RIPEM ($ripempass{$ripemuser}."\n"); + close (RIPEM); + $status = $?; + $err = &read_and_delete ($errfile); + &pdv ($err); + # Since RIPEM status codes are not very informative, perhaps we + # want to check for the existence of the output file, instead. + if ($status >= 0 && $status < 512) { + &delete_tmpfile ($body); + &extract_mime_fields (); + $new_body = &mknonbin ($new_body); + push (@deliver_headers, "MIME-Version: 1.0\n"); + $new_body = &absorb_mime_headers ($new_body); + &decode_smime_auth ($err); + &decode_body ($new_body, '', 0); + } else { + &pdv ("RIPEM exited with status $status\n"); + &delete_tmpfile ($new_body); + &decode_smime_auth ($err); + &decode_nothing ($body); + } +} + +sub verify_smime { +# $err = &verify_smime ($signed_file, $signature, $mic) +# Try to verify the signature of $signed file. +# +# Results are sent to premail auth mechanism. + my ($signed_file, $signature, $mic) = @_; + my ($new_body, $errfile, $err); + my ($invoc); + + &load_secrets (); + if (!defined $ripemuser) { + &error ("Must specify \$ripempass{''} = ''; in secrets file\n"); + } + $new_body = &tmp_filename (); + $invoc = &tilde_expand ($config{'ripem'}); + $invoc .= ' -d -M pkcs -B -k -'; + if (defined $ripemuser) { $invoc .= ' -u '.$ripemuser; } + $body = &force_file_body ($body); + $invoc .= ' -x '.$signed_file; + $invoc .= ' -i '.$signature; + if (defined $mic && $mic != '') { $invoc .= ' -a '.$mic; } + $errfile = &tmp_filename (); + $invoc .= ' > '.$errfile.' 2>&1'; + &pdv ("Invoking RIPEM as $invoc\n"); + if (!open (RIPEM, "|$invoc")) { + &error ("Error invoking RIPEM\n"); + } + print RIPEM ($ripempass{$ripemuser}."\n"); + close (RIPEM); + $status = $?; + $err = &read_and_delete ($errfile); + &pdv ($err); + if ($status >= 0 && $status < 512) { + &decode_smime_auth ($err); + } else { + &pdv ("RIPEM exited with status $status\n"); + } + return ''; +} + +sub decode_smime_auth { +# &decode_smime_auth ($err) +# Convert ripem's stderr output into a premail auth string, and add to the +# premail auth. + my ($err) = @_; + my ($auth); + + $auth = ''; + if ($err =~ /\nSignature status\: ([^\.]+)\./s) { + $auth = $1.' signature'; + } + if ($err =~ /\nReceived [^\n]* encrypted message/s) { + if ($auth) { $auth = 'Decrypted '.lc $auth; } + else { $auth = 'Decrypted'; } + } elsif ($err =~ /\nReceived enveloped-only message/s) { + $auth = 'S/MIME Decrypted'; + } elsif ($err =~ /\nReceived certificates\-and\-CRLs\-only message/s) { + $auth = 'Received certificates and CRLs only'; + } elsif ($err =~ /\nReceived CRL message/s) { + $auth = 'Received CRL only'; + } + if ($auth && $err =~ /\nSender name\: ([^\n]+)\n/s) { + $auth .= ' from '.$1; + } + if ($auth) { &premail_auth ($auth); } + else { &premail_auth ('RIPEM: '.$err); } # cases we did't get! +} + +########################################## +# movemail masquerade + +sub move { + my ($in, $out) = @_; + my ($movemail); + + &set_configs (); + $move_fn = $out; + $move_work_fn = $out.'.'.$$; + push (@open_tmpfiles, $move_work_fn); + $movemail = &tilde_expand ($config{'movemail'}); + $status = system "$movemail $in $out"; + if ($status) { exit $status >> 8; } + if (!sysopen(MOVE_OUT,$move_work_fn,&O_WRONLY|&O_CREAT|&O_EXCL,0600)) { + &error ("Can't open $move_work_fn for writing\n"); + } + select MOVE_OUT; + &decode ($out); +} + +########################################## +# RSA with hidden keys + +sub catfile { +# $contents = &catfile ($filename) + local (*F); + my $ret = ""; + if (open F, "<" . $_[0]) { + while () {$ret .= $_;} + close (F); + } + else { + die "$_[0]: $!"; + } + $ret; +} + +sub b2a { + my @args = @_; + foreach (@args) { + $_ = pack ("u", $_); + s/\n//gm; + tr/\`\!-\_/A-Za-z0-9\+\//; + } + return wantarray ? @args : $args[0]; +} + +sub a2b { + my @args = @_; + foreach (@args) { + tr/A-Za-z0-9\+\//\`\!-\_/; + s/(.{1,61})/$1\n/g; + $_ = unpack ("u", $_); + } + return wantarray ? @args : $args[0]; +} + +sub killbaks { + my @args = @_; + unlink grep {s/\.pgp$/\.bak/ && $_} @args; +} + +sub testkey { + my ($ring, @recips) = @_; + my $err; + unless (&runpgpwring ($ring, "-fes -u 0x @recips", '', undef, \$err)) { + print STDERR &catfile ($err); + &error ("\nCould not use your keys. " + . "The passphrase must be blank.\n"); + } +} + +sub filecat { + my ($f1, $f2) = @_; + local (*F1, *F2); + unless (open (F1, "<$f1")) {return;} + unless (open (F2, ">>$f2")) {close F1; return;} + while () { + print F2 $_; + } + close F1; + close F2; +} + +sub makebigrings { +# ($pubring, $secring) = &makebigrings () +# Make public and secret keyrings. +# Secret keyring contains all nyms, and user's secring. +# Public keyring contains user's pubring, as well as all remailers. +# This is used for decoding (sr) and signature checking (pr). + my ($pr, $sr) = (&tmp_filename ('.pgp'), &tmp_filename ('.pgp')); + my $PGPPATH = $ENV{'PGPPATH'} ? $ENV{'PGPPATH'} : $ENV{'HOME'} . "/.pgp"; + my $PGP = &tilde_expand ($config{'pgp'}); + local ($_); + + &load_secrets (); + foreach (keys %pgpring) { + my ($tpr, $tsr) = &makerings ($pgpring{$_}); + system ("$PGP +language=en +batchmode +verbose=0 -kx 0x $pr $tpr > /dev/null"); + #filecat ($tpr, $pr); + filecat ($tsr, $sr); + &delete_tmpfile ($tpr); + &delete_tmpfile ($tsr); + } + &filecat ("$PGPPATH/pubring.pgp", "$pr"); + &filecat ("$PGPPATH/secring.pgp", "$sr"); + &filecat (&tilde_expand ($config{'pubring'}), $pr); + return ((-r $pr) ? $pr : undef, (-r $sr) ? $sr : undef); +} + +sub makerings { +# ($pr, $sr) = ($ring, $pubring, @pubkeys) +# Make specialized public and secret keyrings. The $ring argument contains +# base-64 encoded public and secret keyrings for the nym, separated by a +# comma. +# +# In addition, the @pubkeys are extracted from $pubring to the new pubring. + my ($ring, $pubring, @pubkeys) = @_; + $ring =~ s/.*$;//; + my ($pr, $sr) = (&tmp_filename ('.pgp'), &tmp_filename ('.pgp')); + my ($pk, $sk) = a2b (split (/,/, $ring)); + local (*TMP); + +# &pdv ('&makerings ("'.join ('", "', @_)."\")\n"); + foreach ([$pr, $pk], [$sr, $sk]) { + open TMP, ">$$_[0]"; + print TMP $$_[1] if defined $$_[1]; + close TMP; + } + my $PGP = &tilde_expand ($config{'pgp'}); + foreach $id (@pubkeys) { + + # TEMP FIX for lanuage -- will be updated + my $invoc = "$PGP +language=en +batchmode +force +verbose=0 -kx " + . "$id $pr $pubring 2>&1"; + &pdv ("$invoc > /dev/null\n"); + system "$invoc > /dev/null"; + } + &killbaks ($pr, $sr); +# system "pgp -kvv $pr"; +# system "pgp -kvv $sr"; + return ($pr, $sr); +} + +sub runpgpwring { + my ($ring, $cmd, $in, $outfnp, $errfnp) = @_; + my ($pr, $sr) = &makerings ($ring); + my ($invoc, $status, $infile, $outfile, $errfile); + local (*TMP); + + if ($in) { + $infile = &tmp_filename (); + open TMP, ">$infile"; + print TMP $in; + close TMP; + } + + $outfile = &tmp_filename (); + $errfile = &tmp_filename (); + $invoc = &tilde_expand ($config{'pgp'}); + + # TEMP FIX for language -- will be updated + $invoc .= ' +language=en +batchmode +force +verbose=0 '; + $invoc .= " +pubring=$pr +secring=$sr "; + $invoc .= $cmd; + $invoc .= ' < ' . $infile if $infile; + $invoc .= ' > '.$outfile; + $invoc .= ' 2> '.$errfile; + #print STDERR "$invoc\n"; + $status = &open_pgp ($invoc, "\n", ''); + + if ($outfnp) { + $$outfnp = $outfile; + } + else { + delete_tmpfile ($outfile); + } + if ($errfnp) { + $$errfnp = $errfile; + } + else { + delete_tmpfile ($errfile); + } + delete_tmpfile ($sr); + delete_tmpfile ($pr); + return $status; +} + +sub genkeypair { +# $ring = &genkeypair ($id) +# Make a new keypair, and return the public and secret keyrings in ascii +# form (i.e. comma separated base-64 encoded). +# +# Include the remailer public key whose id is given as an argument. + my ($pr, $sr) = &makerings (',', &tilde_expand ($config{'pubring'}), @_); + my ($PGP, $ring); + + $PGP = &tilde_expand ($config{'pgp'}); + + print <<"EOF", "Press return to begin: "; + +PGP must be invoked to generate an RSA keypair for your nym. When +prompted for a key ID, you may type anything you wish, but whatever +you choose should in no way be traceable back to your real identity. +PGP will not accept a completely empty key ID, so if you don\'t want a +key ID, it is recommended that you choose a key ID which is just one +space character. + +If you wish to publish your nym\'s PGP public key, then the key ID +should contain the E-mail address of your nym in angle brackets, for +instance: + Mr. Test + +LEAVE THE PASSPRASE BLANK on the new key. When you are prompted for a +passphrase, simply press return. Premail will protect both your +public and private keys by encrypting your entire keyrings in your +secrets file. + +EOF + ; + + # TEMP FIX for language -- will be updated + if (system ("$PGP +language=en -kg +pubring=$pr +secring=$sr +verbose=0")) { + print STDERR "\nKey generation failed.\n"; + &killbaks ($pr, $sr); + &delete_open_tmpfiles (); + exit 1; + } + + $ring = join (',', b2a (&read_and_delete ($pr), &read_and_delete ($sr))); + &killbaks ($pr, $sr); + &testkey ($ring, @_); + return $ring; +} + +sub importkeys { + my ($kid, $remid) = @_; + my ($pr, $sr) = (&tmp_filename ('.pgp'), &tmp_filename ('.pgp')); + my ($PGP, $ring); + my $defsr = $ENV{'PGPPATH'} ? $ENV{'PGPPATH'} . "/secring.pgp" + : $ENV{'HOME'} . "/.pgp/secring.pgp"; + + $PGP = &tilde_expand ($config{'pgp'}); + foreach $a ("$kid $pr", + "$remid $pr " . &tilde_expand ($config{'pubring'}), + "$kid $sr $defsr") { + + # TEMP FIX for language -- will be updated + my $invoc = "$PGP +language=en +batchmode +force +verbose=0 -kx $a 2>&1"; +# print STDERR "+ $invoc\n"; + my $result = `$invoc`; + unless ($result =~ /^Key extracted/m) { + my ($xid, $xr) = split ' ', $a; + $xr = "default public key ring" unless $xr; + &error ("Could not extract key $xid from $xr\n"); + } + } + + print <<"EOF" . "Press return to begin: "; + +Because the user-ID of your PGP private keys can be determined by +anyone from your private keyring, even without your passprase, premail +will encrypt your entire private keyring in the premail secrets file. + +However, in order to use your private key, you must set the passphrase +to be null. Premail will therefore invoke PGP for you to edit a copy +of your key (your actual keyring will remail unchanged). + +PGP will ask you several questions. Answer no to all questions until +asked if you want to change your passphrase. Then answer yes and +press return to set an empty passphrase. [Sorry, it is not possible +to perform these functions automatically from within an application.] + +EOF + ; + + # TEMP FIX for language -- will be updated + my $invoc = "$PGP +language=en +secring=$sr -ke $kid $pr"; +# print STDERR "+ $invoc\n"; + if (system ($invoc)) { + print STDERR "Edit failed.\n"; + &killbaks ($pr, $sr); + &delete_open_tmpfiles (); + exit 1; + } + + &killbaks ($pr, $sr); + $ring = join (',', b2a (&read_and_delete ($pr), &read_and_delete ($sr))); + &testkey ($ring, $remid); + return $ring; +} + +sub exportnym { + my (@args) = @_; + my ($nym, $remailer); + my (@options); + my ($pass); + my ($addrtail, $addrtail2); + + $error_mode = 'd'; + &set_configs (); + %alias = (); + if (!$config{'encrypt'}) { + &error ("Need to enable PGP to create nyms." + ." Add this to your $config{'preferences'} file:\n" + .'$config{\'encrypt\'} = \'yes\';'."\n"); + } + $interactive = 1; + $| = 1; + if ($#args >= 0) { + $nym = $args[0]; + } else { + $nym = &query ('Nym to export (example johndoe@alpha)', ''); + if ($nym eq '') { exit 0; } + } + &get_remailers (); + if ($nym =~ /^([\w\-]+)\=(.*)$/) { + $remailer = $1; + $nym = $2; + } elsif ($nym =~ /^([^\@]+)\@([^\.]+\..*)$/) { + $nym = $1; + $addrtail2 = $2; + $remailer = ''; + foreach $rem (keys %address) { + $addrtail = $address{$rem}; + $addrtail =~ s/^([^\@]+)\@//; + if ($addrtail2 eq $addrtail) { + $remailer = $rem; + } + } + if (!$remailer) { + &error ("No remailer found with address alias\@$addrtail2\n"); + } + } elsif ($nym =~ /^([^\@]+)\@([\w\-]*)$/) { + $nym = $1; + $remailer = $2; + } else { + &error ("Nym must be of the form remailer=alias\n"); + } + &load_secrets (); + if (!$options{$remailer}) { + &error ("Unknown remailer $remailer\n"); + } + @options = split (/ /, $options{$remailer}); + &error ("Remailer $remailer is not of type 'newnym'\n") + unless (&member ('newnym', @options)); + my $nymid = "$remailer=$nym"; + $old_nym = &find_nym ($nymid); + &error ("No such nym as $nym\@$remailer\n") + unless ($old_nym); + &error ("No RSA key for $nym\@$remailer\n") + unless ($pgpring{$nymid}); + my ($pr, $sr) = &makerings ($pgpring{$nymid}); + print "Public keys are in $pr.\nPrivate keys are in $sr.\n"; + exit 0; +} + +########################################## +# creation and management of nyms + +sub makenym { + my (@args) = @_; + my ($nym, $to, $chain, $chain2, $remailer); + my (@options); + my ($replyblock_fn); + my ($pass, $prefix); + my ($secret, $time); + my ($addrtail, $addrtail2); + my ($newnym, $nymid, $fullname, $fakechains, $signsend, $old_chain); + my ($kid); + + $error_mode = 'd'; + if ($importnym) { + $kid = shift @args; + $kid = &query ('Key ID to use for this nym', '') + unless ($kid); + } + &set_configs (); + %alias = (); + if (!$config{'encrypt'}) { + &error ("Need to enable PGP to create nyms." + ." Add this to your $config{'preferences'} file:\n" + .'$config{\'encrypt\'} = \'yes\';'."\n"); + } + $interactive = 1; + $| = 1; + if ($#args >= 0) { + $nym = $args[0]; + } else { + $nym = &query ('Nym to create (example johndoe@alpha)', ''); + if ($nym eq '') { exit 0; } + } + &get_remailers (); + if ($nym =~ /^([\w\-]+)\=(.*)$/) { + $remailer = $1; + $nym = $2; + } elsif ($nym =~ /^([^\@]+)\@([^\.]+\..*)$/) { + $nym = $1; + $addrtail2 = $2; + $remailer = ''; + foreach $rem (keys %address) { + $addrtail = $address{$rem}; + $addrtail =~ s/^([^\@]+)\@//; + if ($addrtail2 eq $addrtail) { + $remailer = $rem; + } + } + if (!$remailer) { + &error ("No remailer found with address alias\@$addrtail2\n"); + } + } elsif ($nym =~ /^([^\@]+)\@([\w\-]*)$/) { + $nym = $1; + $remailer = $2; + } else { + &error ("Nym must be of the form remailer=alias\n"); + } + &load_secrets (); + if (!$options{$remailer}) { + &error ("Unknown remailer $remailer\n"); + } + @options = split (/ /, $options{$remailer}); + if (&member ('newnym', @options)) { + $newnym = 1; + } + elsif (&member ('alpha', @options)) { + &error ("Can only import RSA keys for 'newnym' class remailers.\n") + if ($importnym); + $pass = &random (128); + } + else { + &error ("Remailer $remailer does not support nyms\n"); + } + $to = $ENV{'USER'}.'@'.$ENV{'HOST'}; + $chain = 2; + $fakechains = 1; + $fullname = ucfirst $nym; + $nymid = "$remailer=$nym"; + $old_nym = &find_nym ($nymid); + if ($old_nym ne '') { + if ($nym{$old_nym} =~ /(\^|^)to\=([^\^]*)(\^|$)/) { + $to = $2; + } + if ($nym{$old_nym} =~ /(\^|^)chain\=([^\^]*)(\^|$)/) { + $old_chain = $2; + if ($old_chain =~ /^\d+$/) { + # Chains will be integer on a refresher delete + $chain = $old_chain + !$newnym; + } else { + $chain = ($old_chain =~ tr/;/;/) + !$newnym; + } + } + if (&member ('newnym', @options)) { + if ($pgpring{$nymid}) { + print "Updating existing nym...\n"; + } + if ($nym{$old_nym} =~ /(\^|^)fakechains\=([^\^]*)(\^|$)/) { + $fakechains = $2; + } + if ($nym{$old_nym} =~ /(\^|^)fullname\=([^\^]*)(\^|$)/) { + $fullname = $2; + } + if ($nym{$old_nym} =~ /(\^|^)signsend\=([^\^]*)(\^|$)/) { + $signsend = $2; + } + } + else { + if ($nym{$old_nym} =~ /(\^|^)pass\=([^\^]*)(\^|$)/) { + $pass = $2; + print "Updating existing nym...\n"; + } + } + } + $signsend = 'n' unless $signsend; + if ($#args >= 1) { + $to = $args[1]; + } elsif ($#args < 0) { + $to = &query ('Your e-mail address', $to); + } + if ($to ne 'delete') { + if ($to =~ /\@[\w\-]+$/) { + &error ("Need fully qualified domain name in e-mail address\n"); + } + if ($#args >= 2) { + $chain = $args[2]; + } elsif ($#args < 0) { + $chain = &query ('Number of remailers to use', $chain); + } + # Choosing the chain should be done with awareness that the remailer + # is part of the chain. Thus, we append the remailer to the chain + # and then strip it off. The code assumes that the remailer matches + # /^[\w\-\]+$/ . Technically, the remailer should be added to the + # beginning of the chain, but choose_chain is not smart enough to + # deal with that. + $chain = &choose_chain ($chain.';'.$remailer, 1); + $chain =~ s/(\;|^)[\w\-]+$//; + &pfi ("Creating nym $nym\@$remailer -> $to through $chain\n"); + $chain = &add_random_eks ($chain); + $replyblock_fn = &make_reply_block ($to, $chain); + } + $addrtail = $address{$remailer}; + $addrtail =~ s/^([^\@]+)\@//; + if (!$old_nym && $to eq 'delete') { + &delete_open_tmpfiles (); + print "Could not find nym '$nym\@$addrtail' to delete.\n"; + exit 1; + } + if ($newnym && !$pgpring{$nymid}) { + if ($importnym) { + $pgpring{$nymid} = &importkeys ($kid, $address{$remailer}); + } + else { + $pgpring{$nymid} = &genkeypair ($address{$remailer}); + } + } + if (!$newnym) { + $prefix = 'From: '.$nym.'@'.$addrtail."\n"; + $prefix .= 'Password: '.$pass."\n"; + if ($to eq 'delete') { + $prefix .= 'New-Password:'."\n\n"; + $replyblock_fn = &tmp_filename (); + open (TMP, '>'.$replyblock_fn) || die "Couldn't open file: $!"; + close (TMP); + } else { + $prefix .= 'Reply-Block:'."\n"; + $prefix .= '::'."\nAnon-"; + } + } + else { + my ($pk, $err); + $prefix = "Config:\nFrom: ".$nym.'@'.$addrtail."\n"; + unless (&runpgpwring ($pgpring{$nymid}, "-fkxa 0x", + '', \$pk, \$err)) { + print STDERR ("Failed to extract public key\n", + &read_and_delete ($err)); + &delete_open_tmpfiles (); + exit 1; + } + $pk = &read_and_delete ($pk); + delete_tmpfile ($err); + $prefix .= "Public-Key:\n$pk"; + if ($to eq 'delete') { + $prefix .= "Nym-Commands: delete\n"; + } else { + my @rbs; + my ($rb, $ek, $fakechain, $first, $i, $fn); + + $fakechains = &query ('Number of fake reply blocks', $fakechains) + if ($#args < 0); + + $ek = &random (128); + $rb = "Reply-Block:\n::\nLatent-Time: +0:00\n" + . "Encrypt-Key: $ek\nAnon-" + . &read_and_delete ($replyblock_fn) . "**\n"; + @rbs = ($rb); + $first = ($chain =~ /^([^.;\^]*)/)[0]; + $chain = "$remailer.Encrypt-Key: $ek" . ($chain ? ";$chain" : ""); + + for ($i = 0; $i < $fakechains; $i++) { + if ($config{'numshuf'}) { + $config{'numshuf'} *= 2; + $config{'numshuf'} = 100 if $config{'numshuf'} > 100; + } + else { + $config{'numshuf'} = 4; + } + + $fakechain = ($chain =~ tr/;/;/); + $fakechain = &choose_chain ($fakechain.';'.$remailer); + $fakechain =~ s/(\;|^)[\w\-]+$//; + + &pfi ("Adding fake chain $nym\@$remailer -> nobody " + . "through $fakechain\n"); + $fakechain = &add_random_eks ($fakechain); + $replyblock_fn = &make_reply_block ($to, $fakechain); + $rb = "Reply-Block:\n::\nLatent-Time: +0:00\n" + . "Encrypt-Key: " . &random (128) . "\nAnon-" + . &read_and_delete (&make_reply_block ("nobody", + $fakechain)) + . "**\n"; + splice (@rbs, vec (&random (24), 0, 32) % (1 + @rbs), + 0, ($rb)); + } + + if ($#args < 0) { + $fullname = &query ('Full name of pseudonym (not just ' + . 'E-mail address)', $fullname); + $fullname =~ s/[\'\^\n]//g; # kludge for secrets file + $signsend = &query ('Sign mail with (R)emailer key, ' + . '(P)seudonym key or (N)o key?', + $signsend); + $signsend = substr ($signsend, 0, 1); + $signsend =~ tr/A-Z/a-z/; + $signsend = 'n' unless $signsend =~ /^[rp]$/; + } + $fn = $fullname; + $fn =~ s/([\\\"])/\\$1/g; + $fn = "\"$fn\""; + + $prefix .= "Nym-Commands: create" + . (($old_nym || $importnym) ? "? " : " ") + . ($config{'ack'} ? '+' : '-') . "acksend " + . ($signsend eq 'p' ? '+' : '-') . "fingerkey " + . "-signsend +cryptrecv -disable name=$fn\n"; + $prefix .= join ('', @rbs); + } + $replyblock_fn = &tmp_filename (); + open (TMP, '>'.$replyblock_fn) || die "Couldn't open file: $!"; + close (TMP); + } +# print $prefix; +# print "Here's the reply block:\n"; +# system "cat $replyblock_fn"; + if (&member ('pgp', @options)) { + $key = $address{$remailer}; + } else { + $key = $remailer; + } + if (!$newnym) { + ($replyblock_fn, $err) = + &pgp_encrypt + ($replyblock_fn, $prefix, '', '', + &tilde_expand ($config{'pubring'}), $key); + } + else { + ($replyblock_fn, $err) = + &pgp_encrypt ($replyblock_fn, $prefix, 'ring', $nymid, '', $key); + } +# print "Here's the encrypted block:\n"; +# system "cat $replyblock_fn"; + $time = time; + if (&member ('newnym', @options)) { + $secret = "\$nym\{\'$time\,$remailer\=$nym\'\} \= ". + "\'chain=$chain\^to=$to^" + . "fakechains=$fakechains^fullname=$fullname^" + . "signsend=$signsend\'\;\n"; + } + else { + $secret = "\$nym\{\'$time\,$remailer\=$nym\'\} \= ". + "\'pass=$pass\^chain=$chain\^to=$to\'\;\n"; + } + &pdv ($secret); + # Need to add $remailer to chain as above. + $chain2 = 3; + if ($#args >= 3) { + $chain2 = $args[3]; + } elsif ($#args < 0) { + $chain2 = &query ('Number of remailers for sending request', $chain2); + } + $chain2 = &choose_chain ($chain2); + unless ($config{'debug'} =~ /y/) { + &add_secret ($secret); + &add_secret ('$pgpring{\''.$nymid.'\'} = \'' + .$pgpring{$nymid}. '\';' . "\n", 1); + } + &send_nym_request ($address{$remailer}, $chain2, $replyblock_fn); + print "Sent nym request through $chain2\n"; + print "If no response in 24 hours, try again.\n"; + &delete_open_tmpfiles (); + exit 0; +} + +sub query { +# $result = &query ($query_string, $default) + my ($query_string, $default) = @_; + my ($result); + + if ($default eq '') { + print "$query_string: "; + } else { + print "$query_string [$default]: "; + } + $result = ; + chop $result; + if ($result eq '') { $result = $default; } + return $result; +} + +sub add_random_eks { +# $chain = &add_random_eks ($chain) +# Add random Encrypt-Key:'s to each of the remailers in the chain that +# support it. + my ($chain) = @_; + my (@chain, @new_chain); + my (@options, $pass); + + @chain = split (/\;/, $chain); + @new_chain = (); + foreach $remailer (@chain) { + @options = split (/ /, $options{$remailer}); + if (&member ('ek', @options) && (&member ('pgp', @options) + || &member ('pgp.', @options))) { + $pass = &random (128); + push (@new_chain, $remailer.'.Encrypt-Key: '.$pass); + } else { + push (@new_chain, $remailer); + } + } + return join (';', @new_chain); +} + +sub make_reply_block { +# $replyblock_fn = &make_reply_block ($to, $chain) +# +# Note: this function duplicates a bunch of function from main. + my ($to, $chain) = @_; + my ($replyblock_fn); + + $replyblock_fn = &tmp_filename (); + open (REPLY, '>'.$replyblock_fn) || die "Couldn't open file: $!"; + print REPLY "To: $to\n"; + print REPLY "Chain: $chain \n" if $chain; + print REPLY "\n"; + close (REPLY); + + # Prepare to run premail -edit on the replyblock. + $edit = 1; + $editfile = $replyblock_fn; + push (@open_tmpfiles, $editfile.'~'); # Take care of backup file + if (!&open_input ()) { + &error ("Internal error opening replyblock\n"); + } + &get_header ('-'); + &clear_alias (); + &find_recips (); + &prepare_send_header (); + &compute_groups (); + &close_input (); + if ($#groups + 1 != 1) { + &error ("Internal error: more than one recipient group\n"); + } + &send_group ($groups[0]); + &close_input (); + return ($replyblock_fn); +} + +sub send_nym_request { +# &send_nym_request ($to, $chain, $body) +# +# Note: this function duplicates a bunch of function from main, and also +# breaks many abstractions. + my ($to, $chain, $body) = @_; + + $in_body = $body; + $edit = 0; + $dasht = 1; + if (!open (IN, $body)) { + &error ("Internal error opening replyblock\n"); + } + $in_active = 1; + @in_headers = ("To: $to\n"); + push (@in_headers, "Chain: $chain\n") if $chain; + $header_sep = "\n"; + &clear_alias (); + &find_recips (); + &prepare_send_header (); + &compute_groups (); + &close_input (); + if ($#groups + 1 != 1) { + &error ("Internal error: more than one recipient group\n"); + } + &send_group ($groups[0]); + close (IN); + $in_active = 0; +} + +sub find_nym { +# $full_nym = &find_nym ($short_nym) +# Find a nym's full version (i.e. including a timestamp). Return '' if +# not found. + my ($short_nym) = @_; + + foreach $nym (@nym_list) { + if ($nym =~ /^\d+\,(.*)$/) { + if ($1 eq $short_nym) { return $nym; } + } + } + return ''; +} + +########################################## +# The characterize subsystem + +sub characterize { +# Don't use this unless you really know what you're doing. + my ($remailer, $target, $test) = @_; + my ($all); + + $error_mode = 'd'; + &set_configs (); + $all = ($test eq 'all'); + if ($all || $test eq 'ek') { + $replyblock_fn = &make_reply_block ($target, + $remailer.'.Encrypt-Key: test'); + open (RB, ">>$replyblock_fn") || die "Couldn't open file: $!"; + print RB "Test of ek functionality of $remailer." + ." This line must be encrypted.\n"; +# print RB "**\n"; +# print RB "-----BEGIN PGP JUNK-----\n"; +# print RB "-----END PGP JUNK-----\n"; + close (RB); + system "cat $replyblock_fn"; + system "/usr/lib/sendmail -oi -t < $replyblock_fn" + unless $config{'debug'} =~ /y/; + } + &delete_open_tmpfiles (); + exit 0; +} + +########################################## +# login and logout + +sub login { + my ($x); + + $error_mode = 'd'; + &set_configs (); + foreach $arg (@_) { + if ($arg eq '-x') { + $x = 1; + } + } + &do_login ($x); + &delete_open_tmpfiles (); + exit 0; + +} + +sub logout { + my ($ps, $ps_pgp); + my ($go, $status); + + $error_mode = 'd'; + &set_configs (); + $interactive = 1; + $ps = &tilde_expand ($config{'premail-secrets'}); + $ps_pgp = &tilde_expand_mkdir ($config{'premail-secrets-pgp'}); + if (!-e $ps) { + if (!-e $ps_pgp) { + &error ("No premail secrets file set up. For info on how to set up" + ." the premail\nsecrets, see:\n" + ." http://www.c2.net/~raph/premail/index.html#secrets\n"); + } + &error ("Not logged in!\n"); + } + &load_secrets (); + if (!$premail_pass) { + &error ("No premail password defined. To set up" + ." the premail\npassword, try:\n" + ." premail -setpass"); + } + $go = 1; + if (-e $ps_pgp) { + # Check to see whether secrets have changed, and update only if so. + $status = &decrypt_secrets ($ps_pgp, $ps.'~', $premail_pass); + $go = $status || &cmp_file ($ps, $ps.'~'); + unlink ($ps.'~'); + } + if ($go) { + &encrypt_secrets ($ps_pgp, $ps, $premail_pass); + } + $status = &decrypt_secrets ($ps_pgp, $ps.'~', $premail_pass); + $status ||= &cmp_file ($ps, $ps.'~'); + unlink ($ps.'~'); + if ($status) { + &error ("Error encrypting secrets file: decryption doesn't match\n"); + } + unlink ($ps); + &delete_open_tmpfiles (); + exit 0; +} + +sub cmp_file { +# $different = &cmp_file ($file1, $file2) + my ($file1, $file2) = @_; + my ($l2); + + open (F1, $file1) || die "Couldn't open F1: $!"; + open (F2, $file2) || die "Couldn't open F2: $!"; + while () { + $l2 = ; + if ($_ ne $l2) { close (F1); close (F2); return 1; } + } + close (F1); + if () { close (F2); return 1; } + close (F2); + return 0; +} + +sub setpass { + my ($pass); + + $error_mode = 'd'; + &set_configs (); + &load_secrets (); + $pass = &getpass (); + if ($pass =~ /\'/) { + &error ("Passphrase can't have apostrophe (') in it."); + } + &add_secret ('$premail_pass = \''.$pass.'\';'."\n", 1); + print "Now logged in with new passphrase\n"; + &delete_open_tmpfiles (); + exit 0; +} + +########################################## +# Ripem key generation + +sub ripemkey { + my (@args) = @_; + my ($user, $pass); + + $error_mode = 'd'; + &set_configs (); + $interactive = 1; + $| = 1; + if ($#args >= 0) { + $user = $args[0]; + } else { + $user = $ENV{'USER'}.'@'.$ENV{'HOST'}; + $user = &query ('Your e-mail address (RIPEM user id)', $user); + if ($user eq '') { exit 0; } + } + &load_secrets (); + $pass = &random (128); + if (!open (RIPEM, '|'.&tilde_expand ($config{'ripem'}) + ." -G -b 1024 -u $user -k - -C ".&random (128))) { + &error ("Error invoking RIPEM - maybe you need to set $config{'ripem'}\n"); + } + print RIPEM ($pass."\n"); + print RIPEM ("E\n"); + print RIPEM ($user."\n"); + print RIPEM ("\n"); + close (RIPEM); + if ($?) { + &error ("Error generating RIPEM key\n"); + } + &add_secret ('$ripempass{\''.$user.'\'} = \''.$pass.'\';'."\n", 1); + print "RIPEM key for $user generated\n"; + &delete_open_tmpfiles (); + exit 0; +} + +########################################## +# The prototype GIST server + +sub gist { +# Serve a GIST interface. + my ($buf, $nbytes); + my ($rin, $win, $ein); + my ($cmdbuf, $cmd); + my ($quit, $ineof); + my (@hold_active_chans); + + $error_mode = 'd'; + &set_configs (); + + # GIST globals + @chandir = (); # 'r' = reading (from engine), 'w' = writing, '' = idle + @chanbuf = (); + @chanf = (); + @chanstat = (); # 0 = functioning, 1 = eof, 2 = error + %chanpid = (); # pid associated with each channel + $bufsize = 1024; + $stdin_chan = -1; # -1 = command, otherwise channel for 'write' command + $stdin_cnt = 0; + $stdin_eof = 0; + $select_cmd = 0; + @active_chans = (); # channels with pipes connected + @pid_chans = (); # channels associated with each pid + $gist = 1; + + # Make STDIN (channel from GIST client) nonblocking. + fcntl (STDIN, F_SETFL, O_NONBLOCK | fcntl (STDIN, F_GETFL, $buf)); + + # The main loop + $quit = 0; + $ineof = 0; + while (!$quit) { + $rin = $win = $ein = ''; + vec ($rin, fileno(STDIN), 1) = 1 unless $ineof; + foreach $chan (@active_chans) { +# print "$chan $chandir[$chan] ".length ($chanbuf[$chan]) +# ." $chanstat[$chan]\n"; + if ($chandir[$chan] eq 'r' + && (length $chanbuf[$chan]) != $bufsize) { +# print "chan $chan selected for read\n"; + vec ($rin, fileno($chanf[$chan]), 1) = 1; + } elsif ($chandir[$chan] eq 'w' + && ($chanbuf[$chan] ne '' || $chanstat[$chan])) { +# print "chan $chan selected for write\n"; + vec ($win, fileno($chanf[$chan]), 1) = 1; + } + } + select ($rin, $win, $ein, undef); + if (vec ($rin, fileno(STDIN), 1) || $select_cmd) { + if (vec ($rin, fileno(STDIN), 1)) { + if ($stdin_chan == -1) { + $nbytes = $bufsize; + } else { + $nbytes = $stdin_cnt; + } + $nbytes = sysread STDIN, $buf, $nbytes; + if ($nbytes eq 0) { $ineof = 1; } + if ($stdin_chan eq -1) { + $cmdbuf .= $buf; + } else { + $chanbuf[$stdin_chan] .= $buf; + $stdin_cnt -= length $buf; + if ($stdin_cnt == 0) { + $chanstat[$stdin_chan] = 1 if $stdin_eof; + $stdin_chan = -1; + } + } + } + if ($select_cmd) { + if ($cmdbuf =~ /^\n/) { + &respond ("201 Unselect\n"); + $select_cmd = ''; + } else { + &gist_command ($select_cmd); + } + } + while (!$select_cmd && $cmdbuf =~ /^(\n?)([^\n]+\n)(.*)$/s) { + # Handle an input command + &gist_command ($2); + $cmdbuf = $3; + } + $quit ||= $ineof; + } + @hold_active_chans = @active_chans; + foreach $chan (@hold_active_chans) { + if ($chandir[$chan] eq 'r' + && (length $chanbuf[$chan]) != $bufsize + && vec ($rin, fileno($chanf[$chan]), 1)) { +# print "chan $chan ok for read!\n"; + $nbytes = $bufsize - length $chanbuf[$chan]; + $nbytes = sysread $chanf[$chan], $buf, $nbytes; +# print "Read $nbytes from chan $chan\n"; + if ($nbytes) { + $chanbuf[$chan] .= $buf; + } else { + $chanstat[$chan] = 1; + close ($chanf[$chan]); + &inactivate_chan ($chan); + } + } elsif ($chandir[$chan] eq 'w' + && ($chanbuf[$chan] ne '' || $chanstat[$chan]) + && vec ($win, fileno($chanf[$chan]), 1)) { +# print "chan $chan ok for write!\n"; + $nbytes = length $chanbuf[$chan]; + $nbytes = syswrite $chanf[$chan], $chanbuf[$chan], $nbytes; + $chanbuf[$chan] = substr ($chanbuf[$chan], $nbytes); +# print "$chan stat $chanstat[$chan] nbytes $nbytes\n"; + if ($chanstat[$chan]) { +# print "Closed $chanf[$chan]\n"; + close ($chanf[$chan]); + &inactivate_chan ($chan); + if ($chanbuf[$chan] eq '') { + &close_chan ($chan); + } + } + } + } + } + &delete_open_tmpfiles (); + exit 0; +} + +sub gist_command { + my ($cmd) = @_; + my ($nonzero, $status, $resp); + my (@st_code) = ('', '.', '?'); + my ($ch, $ch1, $ch2, $ch3); + my ($f1, $f2, $f3); + my ($pid); + + # Low level primitives + if ($cmd =~ /^ping\s/) { + &respond ("250 Pong\n"); + } elsif ($cmd =~ /^select (.*)$/) { + $resp = '250 Status'; + $nonzero = 0; + foreach $ch (split (/ /, $1)) { + $resp .= ' '; + if ($chandir[$ch] eq 'r') { + $status = (length $chanbuf[$ch]).$st_code[$chanstat[$ch]]; + } elsif ($chandir[$ch] eq 'w') { + $status .= $bufsize - length $chanbuf[$ch]; + } + $nonzero ||= ($status ne '0');; + $resp .= $status; + } + if ($nonzero) { + $select_cmd = ''; + &respond ($resp."\n"); + } else { + $select_cmd = $cmd; + } + } elsif ($cmd =~ /^read (\d+) (\d+)$/) { + $nbytes = $2; + if (length $chanbuf[$1] < $nbytes) { $nbytes = length $chanbuf[$1]; } + &respond ("250 Read $nbytes\n"); + &respond (substr ($chanbuf[$1], 0, $nbytes)); + $chanbuf[$1] = substr ($chanbuf[$1], $nbytes); + if ($chanbuf[$1] eq '' && $chanstat[$1] == 1) { + &close_chan ($1); + } + } elsif ($cmd =~ /^write (\d+) (\d+)(\.?)$/) { + &respond ("250 Write $2\n"); + if ($2) { + $stdin_chan = $1; + $stdin_cnt = $2; + if ($3) { $stdin_eof = 1; } + } elsif ($3) { $chanstat[$1] = 1; } + # + # The actual server commands + # + } elsif ($cmd =~ /^Test.echo\s/) { + ($f1, $ch1) = &new_chan ('w'); + ($f2, $ch2) = &new_chan ('r'); + push (@active_chans, $ch1, $ch2); + if (!($pid = fork ())) { + &close_all_chanfs (); + &echo ($f1, $f2); + } + close ($f1); close ($f2); + ®ister_pid ($pid, $ch1, $ch2); + &respond ("250 Opened $ch1 $ch2\n"); + } elsif ($cmd =~ /^Mail.capabilities\s/) { + $ch = &alloc_chan ('r'); + $chanbuf[$ch] = "Accept: application/pgp\n" + ."Accept: application/x-pgp\n" + ."Accept: multipart/security\n" + ."Accept: multipart/encrypted\n" + ."Accept: text/plain; lineprefix=\"-----BEGIN PGP \"\n"; + $chanstat[$ch] = 1; + &respond ("250 Opened $ch\n"); + } elsif ($cmd =~ /^Mail.in\s/) { + ($f1, $ch1) = &new_chan ('w'); + ($f2, $ch2) = &new_chan ('r'); + ($f3, $ch3) = &new_chan ('r'); + push (@active_chans, $ch1, $ch2, $ch3); + if (!($pid = fork ())) { + &close_all_chanfs (); + &gist_decode ($f1, $f2, $f3); + } + close ($f1); close ($f2); close ($f3); + ®ister_pid ($pid, $ch1, $ch2, $ch3); + &respond ("250 Opened $ch1 $ch2 $ch3\n"); + } else { + &respond ("500 Command unrecognized\n"); + } +} + +sub alloc_chan { +# $new_chan = &alloc_chan ($dir) + my ($dir, $f) = @_; + my ($chan); + + for ($chan = 0; $chandir[$chan]; $chan++) {} + $chandir[$chan] = $dir; + $chanf[$chan] = ''; + $chanstat[$chan] = 0; + return $chan; +} + +sub new_chan { +# ($f, $new_chan) = &new_chan ($dir) +# Open a new channel connected to a pipe. + my ($dir) = @_; + my ($chan); + + $chan = &alloc_chan ($dir); + pipe ('R'.$chan, 'W'.$chan); + if ($dir eq 'r') { + $chanf[$chan] = 'R'.$chan; + fcntl ('R'.$chan, F_SETFL, O_NONBLOCK + | fcntl ('R'.$chan, F_GETFL, $buf)); + return ('W'.$chan, $chan); + } elsif ($dir eq 'w') { + $chanf[$chan] = 'W'.$chan; + fcntl ('W'.$chan, F_SETFL, O_NONBLOCK + | fcntl ('W'.$chan, F_GETFL, $buf)); + return ('R'.$chan, $chan); + } +} + +sub close_chan { +# &close_chan ($chan) + my ($chan) = @_; + my (@new_pid_chans, $pid); + +# print "close_chan $chan\n"; + if ($chanpid[$chan]) { + $pid = $chanpid{$chan}; + foreach $cha ($pid_chans[$pid]) { + if ($cha != $chan) { + push (@new_pid_chans, $cha); + } + } + $pid_chans{$pid} = join (',', @new_pid_chans); + if ($#new_pid_chans < 0) { + waitpid ($pid, 0); + delete $pid_chans{$pid}; + } + } + $chandir[$chan] = ''; + $chanbuf[$chan] = ''; + $chanpid[$chan] = ''; +} + +sub respond { +# Respond. Does the same thing as print, but uses syswrite + my ($line) = @_; + + syswrite STDOUT, $line, length $line; +} + +sub inactivate_chan { +# Remove $chan from @active_chans + my ($cha) = @_; + my (@new_active) = (); + + foreach $ch (@active_chans) { + if ($ch != $cha) { + push (@new_active, $ch); + } + } + @active_chans = @new_active; +} + +sub close_all_chanfs { + foreach $ch (@active_chans) { +# print "close_all_chanfs: closing $chanf[$ch]\n"; + close ($chanf[$ch]); + } +} + +sub register_pid { + my ($pid, @chans) = @_; + + $pid_chans{$pid} = join (',', @chans); + foreach $ch (@chans) { + $chanpid = $pid; + } +} + +# Handlers for actual commands + +sub echo { + my ($f1, $f2) = @_; + +# sleep (10); + select ($f2); $| = 1; + while (<$f1>) { + print $f2 $_; + } + close ($f1); + close ($f2); + exit 0; +} + +sub gist_decode { + my ($f1, $f2, $f3) = @_; + my ($key, $val); + my (@new_headers); + + open (STDIN, "<&$f1"); + open (STDOUT, ">&$f2"); + open (STDERR, ">&$f3"); + + $error_mode = 'g'; + + &open_input (); + &get_header ('-'); + @deliver_headers = @in_headers; + &decode_body ($in_body, '', 0); + &delete_open_tmpfiles (); + exit 0; +} + +########################################## +# Routines to get files from the Web (experimental) + +# Should we disable all the socket stuff if the config specifies +# getting the file through a command (eg, Lynx)? + +use Socket; + +sub open_web { +# $success = &open_web ($url) +# Open a Web connection for the file as file handle WWW. + my ($url) = @_; + my ($host, $port, $suf); + my ($fqdn, $aliases, $type, $len); + my ($name, $proto); + my ($that); + my ($savesel, $gotsep); +# my ($thishost, $this, $thisaddr); + + if ($config{'geturl'}) { + &pfi ("Getting $url using command $config{'geturl'}\n"); + return (open (WWW, $config{'geturl'}.' '.&shell_quote ($url).'|')); + } + &pfi ("Getting $url\n"); + if ($url =~ /^http\:\/\/([\w\-\.]+)(\:\d+)?(\/.*)$/) { + $host = $1; + $port = $2; + $suf = $3; + if (defined $port && $port =~ /^\:(\d+)$/) { $port = $1; } + else { $port = 80; } + ($fqdn, $aliases, $type, $len, $thataddr) = gethostbyname ($host); + return &pdv ("Host not found: $host\n") if ($thataddr eq ''); +# chop($thishost = `hostname`); + ($name, $aliases, $proto) = getprotobyname("tcp"); +# ($name, $aliases, $type, $len, $thisaddr) = gethostbyname($thishost); + socket (WWW, PF_INET, SOCK_STREAM, $proto) + || return &pdv ("socket: $!\n"); +# $this = pack('S n a4 x8', AF_INET, 0, $thisaddr); + $that = pack('S n a4 x8', AF_INET, $port, $thataddr); + &pdv (sprintf ("connecting to %d.%d.%d.%d:%d\n", + unpack ('C4', $thataddr), $port)); + eval { + $SIG{'ALRM'} = sub { die "Timeout error on $url\n" }; + alarm (10); +# bind(WWW, $this) || &die_disarm ("bind: $!\n"); +# &pdv ("bound the socket...\n"); + connect(WWW, $that) || &die_disarm ("connect: $!\n"); + &pdv ("connected to the socket...\n"); + $savesel = select (WWW); $| = 1; select ($savesel); + print WWW "GET $suf HTTP/1.0\n" + ."Accept: text/plain, text/html, application/x-pgp-pubring, */*\n" + ."User-Agent: premail/$version (perl; unix)\n" + ."\n"; + $response = ; + if ($response !~ /^HTTP\/1\.\d 200/) { + &die_disarm ("Remote server error: $response"); + } + $gotsep = 0; + while (!$gotsep && defined ($_ = )) { + $gotsep = 1 if (/^\r?$/); + } + alarm (0); + $SIG{'ALRM'} = "IGNORE"; + }; + if ($@) { return &pdv ($@); } + return &pdv ("No response from server\n") unless $gotsep; + } elsif ($url =~ /^finger:(.*)$/) { + my $target = @RELAYS ? $1 . '@' . $RELAYS[time % @RELAYS] : $1; + &error("'$target' contains no hostname\n") unless ($target =~ /(.*)@([^@]+)/); + my ($user, $host, $port, $ipaddr, $sin) = ($1, $2); + return &pdv ("Unknown host: $host\n") unless ($ipaddr = inet_aton($host)); + &error ("Internal error: unknown service finger\n"); + socket (WWW, PF_INET, SOCK_STREAM, getprotobyname ('tcp')) || + return &pdv ("socket: $!\n"); + $sin = sockaddr_in ($port, $ipaddr); + connect (WWW, $sin) || return &pdv("S! while connecting to $host\n"); + &pdv ("connected to the socket...\n"); + select ((select(WWW), $|=1)[0]); + + print WWW "$user\r\n"; + } else { + &error ("Misformed URL: $url\n"); + } + return 1; +} + +sub die_disarm { +# Disarm the alarm, then die. Avoids race condition (present in http.ph). + alarm (0); + $SIG{'ALRM'} = "IGNORE"; + die @_; +} + +# Commented out so that we don't depend on GTK, Steve Kostecke 02/27/2000 +# sub create_entry { +# # For some reason, the xterm hack for the passphrase doesn't work +# # on the latest Debian release (2.0Beta). So, I have modified some +# # code from the libgtk-perl package (test.pl program) to pop up a +# # Gtk box to ask for the passphrase. +# # 7/4/98 -- Brent Fulgham +# +# my($box1, $box2, $entry, $button, $separator, $pass_phrase, $label); +# +# init Gtk; +# +# if (not defined $entry_window) { +# $entry_window = new Gtk::Window -toplevel; +# $entry_window->signal_connect("destroy",\&destroy_window,\$entry_window); +# $entry_window->signal_connect("delete_event",\&destroy_window,\$entry_window); +# $entry_window->set_title("Passphrase Entry"); +# $entry_window->border_width(0); +# $box1 = new Gtk::VBox(0,0); +# $entry_window->add($box1); +# show $box1; +# +# $box2 = new Gtk::VBox(0,10); +# $box2->border_width(10); +# $box1->pack_start($box2, 1, 1, 0); +# show $box2; +# +# $entry = new Gtk::Entry; +# $entry->set_usize(0,25); +# $entry->set_visibility(0); +# $entry->select_region(0, length($entry->get_text)); +# $box2->pack_start($entry, 1, 1, 0); +# show $entry; +# +# $separator = new Gtk::HSeparator; +# $box1->pack_start($separator, 0, 1, 0); +# show $separator; +# +# $box2 = new Gtk::VBox(0,10); +# $box2->border_width(10); +# $box1->pack_start($box2,0,1,0); +# show $box2; +# +# $button = new Gtk::Button "Finished"; +# $button->signal_connect("clicked", sub { +# $pass_phrase = $entry->get_text; +# +# destroy_window ($entry_window); +# }); +# $box2->pack_start($button, 1, 1, 0); +# $button->can_default(1); +# $button->grab_default; +# show $button; +# +# $label = new Gtk::Label "Note: No output will appear"; +# $box2->pack_start($label, 1, 1, 0); +# show $label; +# +# } +# if (!visible $entry_window) { +# show $entry_window; +# } +# else { destroy $entry_window }; +# +# main Gtk; +# +# return $pass_phrase; +# } +# +# sub destroy_window { +# my($widget, $windowref, $w2) =@_; +# $$windowref = undef; +# $w2 = undef if defined $w2; +# Gtk->main_quit; +# }