[tor-commits] [trac-email/master] Add perl script to verify gpg and notify python script

hiro at torproject.org hiro at torproject.org
Mon Dec 12 20:48:24 UTC 2016


commit 7ca90ee60f156f87b4cc8be76191ef830824eda8
Author: hiromipaw <hiro at torproject.org>
Date:   Mon Dec 12 21:48:15 2016 +0100

    Add perl script to verify gpg and notify python script
---
 mailverify.pl | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 notify.py     |  71 ++++++++++++++++++++++
 2 files changed, 263 insertions(+)

diff --git a/mailverify.pl b/mailverify.pl
new file mode 100644
index 0000000..aa523b9
--- /dev/null
+++ b/mailverify.pl
@@ -0,0 +1,192 @@
+#!/usr/bin/env perl
+# File Name: mailverify
+# Maintainer: Moshe Kaminsky <kaminsky at math.huji.ac.il>
+# Original Date: September 30, 2003
+# Last Update: September 30, 2003
+# http://mirror.hamakor.org.il/archives/linux-il/att-5615/mailverify
+###########################################################
+
+use warnings;
+use integer;
+
+BEGIN {
+    our $VERSION = 0.1;
+
+    # analyze command line
+    use Getopt::Long qw(:config gnu_getopt);
+    use Pod::Usage;
+
+    our $opt_help;
+    our $opt_man;
+    our $opt_version;
+    our $Gpg;
+    our $Tolerant;
+    GetOptions('gpg=s' => \$Gpg,
+               'tolerant!' => \$Tolerant,
+               'help', 'version', 'man');
+    pod2usage(1) if $opt_help;
+    pod2usage(-verbose => 2) if $opt_man;
+    print "$0 version $VERSION\n" and exit if $opt_version;
+    $Gpg = '/usr/bin/gpg --batch --verify' unless $Gpg;
+}
+
+use File::Temp qw( tempfile );
+
+my $PrevField = '';
+
+# process the header
+while (<>) {
+    next if /^From /o;
+    last if /^$/o;
+    if (/^([\w-]+): (.*)$/o) {
+        $Header{$1} = $2;
+        $PrevField = $1;
+    } else {
+        $Header{$PrevField} .= $_;
+    }
+}
+
+# check that the message is signed
+$Str = $Header{'Content-Type'};
+ at Parts = split /;\s+/, $Str if $Str;
+if (not $Str or $Parts[0] ne 'multipart/signed' ) {
+    # the message is not multipart/signed, but might still be cleartext
+    # signed. Depending on --tolerant, we may pass the rest of the message to
+    # gpg directly
+    print "Message not signed\n" and exit -1 unless $Tolerant;
+    open GPG, "|$Gpg" or die "Can't open pipe to gpg ($Gpg): $!";
+    print GPG <>;
+    close GPG;
+    exit $? >> 8;
+}
+
+# the boundary string signals the boundary between two attachments
+$Boundary = $1 if $Parts[3] =~ /^boundary="(.*)"$/o;
+# go to the start of the message
+while (<>) {
+    last if $_ eq "--$Boundary\n";
+}
+
+# read the message, excluding the last (empty) line
+while (<>) {
+    last if $_ eq "--$Boundary\n";
+    push @Message, $_;
+}
+pop @Message;
+# read the sig
+while (<>) {
+    last if /^-----BEGIN PGP SIGNATURE-----$/o;
+}
+{
+do {
+    push @Sig, $_;
+    last if /^-----END PGP SIGNATURE-----$/o;
+} while (<>);
+};
+
+# here comes the funny part: replace \n by \r\n
+$_ = join '', @Message;
+s/(?<!\r)\n/\r\n/g;
+
+# write everything to files
+my ($MsgFH, $MsgFile) = tempfile;
+print $MsgFH $_;
+my $SigFile = "$MsgFile.asc";
+open SIGFH, ">$SigFile" or die "can't open $SigFile: $!";
+print SIGFH @Sig;
+close $MsgFH;
+close SIGFH;
+
+# run gpg
+print `$Gpg $SigFile`;
+
+# clean up
+unlink $MsgFile, $SigFile;
+
+# exit with the status of gpg
+exit $? >> 8;
+
+__DATA__
+
+# start of POD
+
+=head1 NAME
+
+mailverify - verify the pgp signature of a mime signed mail message
+
+=head1 SYNOPSIS
+
+B<mailverify> B<--help>|B<--man>|B<--version>
+
+B<mailverify> [B<--gpg=I<gpg command>>] [B<--(no)tolerant>] [I<mail file>]
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--gpg=I<gpg command>>
+
+The command to run to do the actual checking. The default is
+S<C</usr/local/bin/gpg --batch --verify>>. It is called with one argument,
+which is the name of the file containing the signature. If B<--tolerant> is
+used, it may also be called with the whole message on the standard input.
+
+=item B<--(no)tolerant>
+
+Normally (with B<--notolerant>), if the Content-Type is not
+C<multipart/signed>, B<mailverify> decides that the message is not signed,
+and exits with status -1. With this switch, the message is passed to I<gpg>
+(or whatever was specified with the B<--gpg> option) as is. This way,
+clearsigned messages can be verified with the same command.
+
+=item B<--help>
+
+Give a short usage message and exit with status 1
+
+=item B<--man>
+
+Give the full description and exit with status 1
+
+=item B<--version>
+
+Print a line with the program name and exit with status 0
+
+=back
+
+=head1 ARGUMENTS
+
+If an argument is given, it is treated a file containing an e-mail message to
+verify, but more common is to read the message from stdin.
+
+=head1 DESCRIPTION
+
+This script verifies the pgp signature of files whose signature appears as an
+attachment (Content-Type C<multipart/signed>). If B<--tolerant> is used, it
+will also verify clearsigned messages. The usage is basically to pipe the
+mail to this program.
+
+=head1 EXIT STATUS
+
+If the message is not of type C<multipart/signed>, and B<--tolerant> is not
+given, the exit status is -1. In any other case, it is the exit status of the
+I<gpg> command.
+
+=head1 SEE ALSO
+
+I<gpg(1)>
+
+=head1 BUGS
+
+Probably plenty, since I don't read any RFCs. Works with all cases I checked,
+though.
+
+=head1 AUTHOR
+
+Moshe Kaminsky <kaminsky at math.huji.ac.il> - Copyright (c) 2003
+
+=head1 LICENSE
+
+This program is free software. You may copy or
+redistribute it under the same terms as Perl itself.
+
+=cut
diff --git a/notify.py b/notify.py
new file mode 100755
index 0000000..f0f384e
--- /dev/null
+++ b/notify.py
@@ -0,0 +1,71 @@
+import sys
+import email
+import re
+from trac.env import Environment
+from trac.ticket.model import Ticket
+from trac.perm import IPermissionRequestor, PermissionCache, PermissionSystem
+
+def read_message:
+    full_msg = sys.stdin.read()
+    return email.message_from_string("".join(full_msg))
+
+def get_message_body(message):
+    body = ""
+    if message.is_multipart():
+        for payload in message.get_payload():
+            # if payload.is_multipart(): ...
+            body.join(payload.get_payload())
+    else:
+        body.join(message.get_payload())
+
+    return body
+
+def verify_user_permissions(env, user, permission):
+    ps = PermissionSystem(env)
+    permissions_list = ps.get_user_permissions(user)
+    return permission_list[permission]
+
+def find_or_create_ticket(message, env):
+    # Find the ticket ID if exists
+    ticket_id = re.search('#(.+?) ', summary)
+
+    # Get the message body
+    body = get_message_body(message)
+    reporter = msg['From']
+
+    if ticket_id:
+        t_id = ticket_id.group(1)
+        tkt = Ticket(env, int(t_id))
+        if verify_user_permissions(env, reporter, 'TICKET_MODIFY'):
+            tkt.save_changes(comment=body)
+    else:
+        if verify_user_permissions(env, reporter, 'TICKET_CREATE'):
+            summary = msg['Subject']
+            cc_list = msg['Cc']
+            component = re.findall(r'\[(.*?)\]', summary)
+            tkt = Ticket(env)
+            tkt['reporter'] = reporter
+            tkt['summary'] = summary
+            tkt['cc'] = cc_list
+            tkt['componet'] = component
+            tkt['description'] = body
+            tkt['status'] = 'new'
+            tkt.insert()
+
+# Open logs file
+f = open('/home/tracadm/log/test.log', 'w')
+f.write('PGP verified\n')
+
+# Define the current trac environment
+env = Environment('/current')
+
+# Build email message
+msg = read_message
+
+# Verify user can create/reply to tickets
+
+# Find or create ticket
+find_or_create_ticket(msg, env)
+
+# Close log file
+f.close



More information about the tor-commits mailing list