bug-mailutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [bug-mailutils] mu-mh questions


From: Sergey Poznyakoff
Subject: Re: [bug-mailutils] mu-mh questions
Date: Sun, 11 Jun 2017 01:12:21 -2100

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: address@hidden
  
Finally, to encrypt the message being composed, do

  edit mhgpg -e

Hope that helps.

Regards,
Sergey
  
#! /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
#! /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 <address@hidden>

=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 address@hidden unless wantarray;

    die "malformed input"
        unless $body;
    return ( address@hidden, $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+(.+)$/address@hidden/) {
                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;
        }
    }
}



reply via email to

[Prev in Thread] Current Thread [Next in Thread]