I think the attached revision addresses points 1. (the parameters
are passed through the input stream), 2. (it doesn't use absolute path
to the Perl executable), 6. (the errors are shown to the user).

I'll need more hints to address points 3. (what more validation
and error checking could be used in the Perl script), 4. (I don't know
how we could usefully sanitize GPG input), 5. (how the 'open'
command should escape parameters before executing them as shell
commands).
From af37cf2575b395af0c4ba30cf04e58a42eb482f5 Mon Sep 17 00:00:00 2001
From: Ineiev <ine...@gnu.org>
Date: Fri, 10 Feb 2017 15:10:55 +0300
Subject: [PATCH] Encrypt message with GPG key when available.

---
 frontend/perl/encrypt-to-user/index.pl  | 146 ++++++++++++++++++++++++++++++++
 frontend/php/account/lostpw-confirm.php |  50 ++++++++++-
 frontend/php/my/admin/index.php         |  18 +++-
 3 files changed, 212 insertions(+), 2 deletions(-)
 create mode 100644 frontend/perl/encrypt-to-user/index.pl

diff --git a/frontend/perl/encrypt-to-user/index.pl b/frontend/perl/encrypt-to-user/index.pl
new file mode 100644
index 0000000..395e3be
--- /dev/null
+++ b/frontend/perl/encrypt-to-user/index.pl
@@ -0,0 +1,146 @@
+#! /usr/bin/perl
+# Encrypt a message to specified savane user.
+#
+# Copyright 2017 (c) Ineiev <ineiev--gnu.org>
+#
+# This file is part of Savane.
+#
+# Savane is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Savane is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use DBI;
+use File::Temp qw(tempdir tempfile);
+use Getopt::Long;
+my $getopt;
+my $help;
+my $user;
+my $sys_dbname;
+my $sys_dbhost;
+my $sys_dbuser;
+my $sys_dbparams;
+my $sys_dbpasswd;
+my $exit_code = 0;
+
+eval {
+    $getopt = GetOptions("help" => \$help,
+                         "user=s" => \$user,
+                         "dbname=s" => \$sys_dbname,
+                         "dbhost:s" => \$sys_dbhost,
+                         "dbparams:s" => \$sys_dbparams);
+};
+
+sub print_help {
+    print STDERR <<EOF;
+Usage: $0 [OPTIONS]
+
+Encrypt a message to user's registered GPG key.
+
+  -h, --help            Display this help and exit
+      --user            Savannah user to encrypt to
+      --dbname          Savannah database name
+      --dbhost          Savannah database host
+      --dbparams        Savannah database parameters
+
+Database user and password are passed in the first two lines of input.
+
+EOF
+}
+
+if($help) {
+    print_help();
+    exit(0);
+}
+
+$sys_dbuser = <> or die "No database user is supplied.";
+$sys_dbpasswd = <> or die "No database password is supplied.";
+
+$sys_dbuser =~ s/\n$//;
+$sys_dbpasswd =~ s/\n$//;
+
+our $dbd = DBI->connect('DBI:mysql:database='.$sys_dbname
+		       .':host='.$sys_dbhost
+		       .$sys_dbparams,
+                       $sys_dbuser, $sys_dbpasswd,
+                       { RaiseError => 1, AutoCommit => 1});
+
+## Encrypt to user GPG key if available
+# arg1: user id
+# arg2: message
+# return encrypted message when encryption succeeded,
+#        empty string encryption failed.
+# Exit codes:
+#   0 when encryption succeeded,
+#   1 when it failed,
+#   2 when no suitable key was found.
+sub UserEncrypt {
+    my ($user, $message) = @_;
+    my $key = $dbd->selectrow_array("SELECT gpg_key FROM user WHERE user_id=".$user);
+
+    return "" unless $key ne "";
+
+    my ($mh, $mname) = tempfile(UNLINK => 1);
+    my $temp_dir = tempdir(CLEANUP => 1);
+    my $input;
+    my $key_id = "";
+    my $msg = "";
+
+    print $mh $message;
+
+    $exit_code = 2;
+    open($input, '|-', 'gpg --homedir='.$temp_dir.' -q --import');
+    print $input $key;
+    close($input) or return "";
+
+# Get the first ID of a public key with encryption capability.
+    open($input, '-|', 'gpg --homedir='.$temp_dir.
+                       ' --list-keys --with-colons 2> /dev/null');
+    while(<$input>)
+      {
+        if(!/^pub/)
+          {
+            next;
+          }
+        my @fields = split /:/;
+        if(@fields[11] !~ /[eE]/)
+          {
+            next;
+          }
+        $key_id = @fields[4];
+        last unless $key_id eq "";
+      }
+    close($input) or return "";
+    return "" unless $key_id ne "";
+    $exit_code = 1;
+    open($input, '-|', 'gpg --homedir='.$temp_dir.
+                       ' --trust-model always --batch -a --encrypt -r '
+                       .$key_id." -o - ".$mname);
+    while(<$input>)
+      {
+        $msg = $msg.$_;
+      }
+    close $input and $exit_code = 0;
+    return "" unless $msg ne "";
+    return $msg;
+}
+
+my $msg = "";
+
+while(<>)
+  {
+    $msg = $msg.$_;
+  }
+
+print UserEncrypt($user, $msg);
+
+exit $exit_code;
diff --git a/frontend/php/account/lostpw-confirm.php b/frontend/php/account/lostpw-confirm.php
index a4dc5cd..62ac359 100644
--- a/frontend/php/account/lostpw-confirm.php
+++ b/frontend/php/account/lostpw-confirm.php
@@ -4,6 +4,7 @@
 # Copyright 1999-2000 (c) The SourceForge Crew
 # Copyright 2004-2005 (c) Mathieu Roy <yeupou--gnu.org>
 #                          Joxean Koret <joxeankoret--yahoo.es>
+# Copyright 2017 (c) Ineiev <ineiev--gnu.org>
 #
 # This file is part of Savane.
 # 
@@ -24,6 +25,7 @@ require_once('../include/init.php');
 require_once('../include/sane.php');
 require_once('../include/session.php');
 require_once('../include/sendmail.php');
+require_once('../include/database.php');
 
 register_globals_off();
 
@@ -140,6 +142,41 @@ $message_for_admin =
 . gmdate('D, d M Y H:i:s \G\M\T')
      . "\n";
 
+$encrypted_message = "";
+$gpg_error = "";
+if(user_get_preference("email_encrypted", $row_user['user_id']))
+  {
+    $cmd = 'perl ../../perl/encrypt-to-user/index.pl '
+    .'--user="'.$row_user['user_id'].'" '
+    .'--dbname="'.$sys_dbname.'" '
+    .'--dbhost="'.$sys_dbhost.'"';
+
+    $d_spec = array(
+        0 => array("pipe", "r"), 1 => array("pipe", "w"),
+        2 => array("file", "/dev/null", "a"));
+
+    $gpg_proc = proc_open($cmd, $d_spec, $pipes, NULL, $_ENV);
+    fwrite($pipes[0], $sys_dbuser."\n");
+    fwrite($pipes[0], $sys_dbpasswd."\n");
+    fwrite($pipes[0], $message);
+    fclose($pipes[0]);
+    $encrypted_message = stream_get_contents($pipes[1]);
+    fclose($pipes[1]);
+    $gpg_result = proc_close($gpg_proc);
+
+    if($gpg_result != 0 or $encrypted_message === FALSE or $encrypted_message === "")
+      {
+        $encrypted_message = "";
+        if($gpg_result == 2)
+          $gpg_error = _("No key for encryption found.");
+        else
+          $gpg_error = _("Encryption failed.");
+      }
+  }
+
+if($encrypted_message != "")
+  $message = $encrypted_message;
+
 sendmail_mail($GLOBALS['sys_mail_replyto']."@".$GLOBALS['sys_mail_domain'],
 	      $row_user['email'],
 	      $GLOBALS['sys_default_domain']." Verification",
@@ -159,6 +196,17 @@ $HTML->header(array('title'=>_("Lost Password Confirmation")));
 
 print '<p>'._("An email has been sent to the address you have on file.").'</p>';
 print '<p>'._("Follow the instructions in the email to change your account password.").'</p>';
-;
+
+if($encrypted_message === "")
+  {
+    if(user_get_preference("email_encrypted", $row_user['user_id']))
+      print '<p><strong>'.$gpg_error.'<strong></p>';
+    print '<blockquote><p>'._("Note that the message was sent unencrypted.
+In order to use encryption, register an encryption-capable GPG key
+and set the <b>Encrypt emails when resetting password</b> checkbox
+in your account settings.").'</p></blockquote>';
+  }
+else
+  print '<p>'._("Note that it was encrypted with your registered GPG key.").'</p>';
 
 $HTML->footer(array());
diff --git a/frontend/php/my/admin/index.php b/frontend/php/my/admin/index.php
index 3b19a28..cbf5e80 100644
--- a/frontend/php/my/admin/index.php
+++ b/frontend/php/my/admin/index.php
@@ -3,6 +3,7 @@
 # Copyright 1999-2000 (c) The SourceForge Crew
 # Copyright 2002-2006 (c) Mathieu Roy <yeupou--gnu.org>
 # Copyright (C) 2007  Sylvain Beucler
+# Copyright (C) 2017 Ineiev <ineiev--gnu.org>
 #
 # This file is part of Savane.
 # 
@@ -30,7 +31,7 @@ extract(sane_import('post',
     'form_keep_only_one_session',
     'form_timezone', 'user_theme', 'theme_rotate_jump',
     'form_reverse_comments_order', 'form_stone_age_menu', 'form_nonfixed_feedback',
-    'form_use_bookmarks', 'form_email_hide',
+    'form_use_bookmarks', 'form_email_hide', 'form_email_encrypted'
     )));
 
 if ($update and $user_theme != "random" and $user_theme != "rotate")
@@ -79,6 +80,12 @@ if ($update)
   else
     { user_unset_preference("use_bookmarks"); }
 
+  # Encryption preferences
+  if ($form_email_encrypted == "1")
+    { user_set_preference("email_encrypted", 1); }
+  else
+    { user_unset_preference("email_encrypted"); }
+
   # Relative position feedback
   if ($form_nonfixed_feedback == "1")
     { user_set_preference("nonfixed_feedback", 1); }
@@ -287,6 +294,15 @@ print '<input type="checkbox" name="form_email_hide" value="1" '.($row_user['ema
 
 print '<p class="smaller">'._("When checked, the only way for users to get in touch with you would be to use the form available to logged-in users. It is generally a bad idea to choose this option, especially if you are a project administrator.").'</p>';
 
+print '<input type="checkbox" name="form_email_encrypted" value="1" '
+.(user_get_preference("email_encrypted") ? 'checked="checked"':'').' /> '
+._("Encrypt emails when resetting password");
+
+print '<p class="smaller">'
+._("When checked, Savannah will encrypt email messages
+with your registered public GPG key when resetting password is requested.
+If no suitable key is available, the messages still go unencrypted.")
+.'</p>';
 
 print $HTML->box_bottom();
 print "<br />\n";
-- 
1.9.1

Attachment: signature.asc
Description: Digital signature

Reply via email to