Re: mu-mh questions

Sergey Poznyakoff <[email protected]>
Newsgroups gmane.comp.gnu.mailutils.bugs
Organization GNU.org.ua
Message-ID <[email protected]>
Hi Pierre-Jean,

On 08 Jun 2017 I wrote:
> I'll return to the problem of using encrypted/signed messages in
> my next email.

The problem of using PGP messages with MH can be subdivided in three
parts: (1) message decryption, (2) message verification,
and (3) message signing and encryption.

To handle the first part, observe that in MIME messages, encrypted
parts have application/octet-stream content type.  Find attached the
script "mhshow", copy it somewhere to your path and make sure it
has executable bit set.  Now add the following to your .mh_profile:

  mhn-show-application/octet-stream: mhshow %F

The script uses file(1) to determine whether it has to do with a
PGP encrypted message, and calls gpg to decrypt it if so.

Whenever you need to read a signed part, run

  mhn -show [-part N] MSG

or "edit mhn -show [-part N] MSG", from the whatnow prompt.
 
Parts (2) and (3) are handled by the script mhgpg, attached herewith.
To verify a PGP-signed message, run

  edit mhgpg

To sign the composed message, run

  edit mhgpg -s

If you have several keys, you can select the right one by placing its ID
in the gpg-user profile entry.  E.g.

  gpg-user: DE45AF11
or
  gpg-user: [email protected]
  
Finally, to encrypt the message being composed, do

  edit mhgpg -e

Hope that helps.

Regards,
Sergey

_______________________________________________
Bug-mailutils mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/bug-mailutils
mhshow (text/x-shellscript, 371 B)
#! /bin/sh
# Simple script to show PGP encrypted MIME parts.
# Usage (from .mh_profile):
#   mhn-show-application/octet-stream: mhshow %F

type=`file -b ${1:?}`
case $type in
"PGP message")
	temp=/tmp/mhshow.$$
	trap "rm -f $temp" TERM QUIT
	if gpg --decrypt $1 > $temp; then
	    mhn -show -file $temp || less -c $temp
	fi
	rm $temp
	;;
*)
	od -c $1 | less
	;;
esac
mhgpg (text/x-perl, 6.7 KB)
#! /usr/bin/perl
use strict;
use warnings;
use Getopt::Long qw(:config gnu_getopt no_ignore_case require_order);
use File::Temp qw(tempfile);
use File::Copy;
use File::Basename;
use Cwd qw(abs_path);
use Pod::Man;
use Pod::Usage;

=head1 NAME

mhgpg - wrapper for verifying, signing, and encrypting messages using GPG

=head1 SYNOPSIS

B<mhgpg>
[B<-ves>]
[B<--verify>]
[B<--encrypt>]
[B<--sign>]
[B<--> I<GPGOPT>...]
I<FILE>    

B<edit mhgpg>
[B<-ves>]
[B<--verify>]
[B<--encrypt>]
[B<--sign>]
[B<--> I<GPGOPT>...]

=head1 DESCRIPTION

Verifies, signs, or encrypting messages from B<MH> using B<GPG>.   The script
is normally invoked from the B<whatnow> prompt as B<edit mhgpg I<OPTIONS>>.

I<OPTIONS> select operation mode.  If the B<--> marker is present, all arguments
that follow it will be passed to the B<gpg> command line.  Another way to pass
additional options to B<gpg> is by specifying them in the B<gpg-options> entry
in F<~/.mh_profile>.  If both methods are used, options from B<gpg-options>
precede those from the command line.

=head1 OPTIONS

=over 4

=item B<-v>, B<--verify>

Verify the GPG signature.  This is the default.

=item B<-s>, B<--sign>    

Sign the message.  The key to sign with can be specified in the B<gpg-user>
profile entry.

=item B<-e>, B<--encrypt>

Encrypt the message.  Can be used together with B<--sign>.    

=back

=head1 AUTHOR

Sergey Poznyakoff <[email protected]>    
    
=cut    

my @gpg = ( 'gpg' );

my $verify;
my $sign;
my $encrypt;

my $input;
my $inputdir;

my %profile;

my @tempfiles;
END {
    unlink @tempfiles if @tempfiles;
}


sub readprofile {
    my $name = $ENV{MH} || "$ENV{HOME}/.mh_profile";
    return undef unless -f $name;
    open(my $fd, '<', $name) or die "can't open profile $name: $!";
    my $line;
    while (<$fd>) {
	chomp;
	if (s/^\s+/ /) {
	    $line .= $_;
        } else {
	    if (defined($line) && $line !~ /^#/ && $line !~ /^$/) {
		my ($kw, $val) = split /:/, $line, 2;
		if (defined($kw) && defined($val)) {
		    $profile{$kw} = $val;
		}
	    }
	    $line = $_;
	}
    }
    if (defined($line) && $line !~ /^#/ && $line !~ /^$/) {
	my ($kw, $val) = split /:/, $line, 2;
	if (defined($kw) && defined($val)) {
	    $profile{$kw} = $val;
	}
    }
    close($fd);
}

sub split_message_parts {
    my $file = shift;
    open(my $fd, '<', $file)
	or die "can't open $file: $!";
    # Split message on header and body parts
    my @headers;
    my $body;
    while (<$fd>) {
	chomp;
	if (/^(-+)?$/) {
	    local $/ = undef;
	    $body = <$fd>;
	    last;
	}
	push @headers, $_;
    }
    close $fd;

    return \@headers unless wantarray;
    
    die "malformed input"
	unless $body;
    return ( \@headers, $body );
}

sub getkeys {
    my ($opt, $input) = @_;
    my $fd;

    open($fd, '-|', "whom $input")
	or die "whom failed";
    my @recp;
    while (<$fd>) {
	chomp;
	s/^\s+//;
	if (/^-- Network Recipients --$/) {
	    push @recp, 1;
	} elsif (/^-- .* --$/) {
	    last;
	} elsif (@recp)	{
	    if (s/^(.+?)\s+at\s+(.+)$/$1\@$2/) {
		push @recp, $_;
	    }
	}
    }
    close $fd;
    
    shift @recp;

    die "no recipients in message"
	unless @recp;

    open($fd, '-|', 'gpg', '--list-keys', '--with-colons', @recp)
	or die "can't list keys";
    while (<$fd>) {
	chomp;
	if (/^pub:/) {
	    my @a = split /:/;
	    print "signing for $a[9]\n";
	    push @gpg, $opt, $a[4]
	}
    }
    close $fd;
}

sub gpg_verify {
    open(STDIN, '<', $input);
    exec(@gpg, '--verify');
}

sub replace_input {
    my $msgfile = shift;
    
    my $backup = $inputdir . '/' . ',' . basename($input);
    unlink $backup if -e $backup;
    rename $input, $backup
	or die "can't rename $input to $backup: $!";
    rename $msgfile, $input
	or die "can't rename $msgfile to $input: $!";
}

sub gpg_sign {
    my $fd;

    push @gpg, '--clearsign';
    
    my ($href, $body) = split_message_parts($input);
    
    ($fd, my $bodyfile) = tempfile('mhgpgXXXXXX',
				   SUFFIX => '.bod',
				   DIR => $inputdir,
				   UNLINK => 1);
    print $fd $body;
    close $fd;

    my $outfile = $bodyfile . '.out';
    push @tempfiles, $outfile;
    
    system(@gpg, '--output', $outfile, $bodyfile);
    if ($? == -1) {
	die "failed to run gpg: $!";
    } elsif ($? & 127) {
	die "gpg died with signal ".($? & 127);
    } else {
	my $code = $? >> 8;
	if ($code) {
	    die "fatal error";
	}
    }
    
    ($fd, my $msgfile) = tempfile('mhgpgXXXXXX',
				  SUFFIX => '.msg',
				  UNLINK => 1,
				  DIR => $inputdir);
    select((select($fd), $|=1)[0]);
    print $fd join("\n", @$href);
    print $fd "\n\n";
    copy($outfile, $fd);
    close($fd);

    replace_input($msgfile);
}

sub gpg_encrypt {
    push @gpg, '--armor';
    getkeys('-r', $input);

    my ($href, $body) = split_message_parts($input);
    
    my $ascfd = tempfile();
    $^F = 255;
    my $pid = fork;
    if ($pid == 0) {
	open(STDOUT, '<&', $ascfd) or die "can't redirect STDOUT";
	open(my $fd, '|-', @gpg) or die "gpg failed";
	print $fd $body;
	close $fd;
	exit !!$?
    }
    my $ret = wait;
    exit(1) if $ret != $pid or $?;
    
    my $bdry = sprintf("%08u-%08u=:%u", int(rand(0xffffffff)),
		       time, $$);
    
    my ($fd, $msgfile) = tempfile('mhgpgXXXXXX',
				  SUFFIX => '.msg',
				  DIR => $inputdir,
				  UNLINK => 1);
    select((select($fd), $|=1)[0]);
    foreach my $h (@$href) {
	print $fd "$h\n";
    }
    print $fd "Mime-Version: 1.0\n";
    print $fd "Content-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"$bdry\"\n";
    print $fd "\n";

    print $fd <<EOT
--$bdry
Content-Type: application/pgp-encrypted
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment

Version: 1

EOT
;
    print $fd <<EOT
--$bdry
Content-Type: application/octet-stream
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="msg.asc"

EOT
;
    seek $ascfd, 0, 0;
    copy($ascfd, $fd);

    print $fd "\n";
    print $fd "--$bdry--\n";

    close($fd);

    replace_input($msgfile);
}

GetOptions("sign|s" => \$sign,
	   "encrypt|e" => \$encrypt,
	   "verify|v" => \$verify,
	   "help|?" => sub {
	       pod2usage(-exitstatus => 0, -verbose => 2);
	   }	   
    ) or exit(1);

pod2usage(-exitstatus => 1, -verbose => 0, -output => \*STDERR)
    if (($sign||$encrypt) && $verify);

$verify = 1
    unless ($sign||$encrypt);

pod2usage(-exitstatus => 1, -verbose => 0, -output => \*STDERR)
    unless @ARGV == 1;

$input = abs_path(pop(@ARGV));
$inputdir = dirname($input);

readprofile;

push @gpg, $profile{'gpg-options'} if exists($profile{'gpg-options'});
push @gpg, @ARGV;

if ($verify) {
    gpg_verify;
} else {
    if ($encrypt) {
	push @gpg, '--encrypt';
	gpg_encrypt
    } elsif ($sign) {
	push @gpg, '-u', $profile{'gpg-user'} if exists($profile{'gpg-user'});
	if ($encrypt) {
	    push @gpg, '--sign', '--encrypt';
	    gpg_encrypt
	} else {
	    gpg_sign;
	}
    }
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.