You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1250 lines
50 KiB

<?php
/////////////////////////////////////////////////////////////////////////////
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
//
// PURPOSE:
// A brief OpenPGP support class for Tiki OpenPGP functionality in
// - webmail
// - ZF based mail
// - newsletters
// - admin notifications
//
//
//
// CHANGE HISTORY:
// v0.10
// 2012-11-04 hollmeer: Collected all functions into intial version openpgplib.php.
// Minimal preparation/calling portions remain in caller sources,
// as it seems so far adequate with current approch to leave
// such portions e.g. there
// NOTE: Zend Framework as is wrapped by bringing/changing
// necessary classes from
// Zend/Mail/ and
// Zend/Mail/Transport/
// into lib/openpgp/ per now. No patches needed anymore
// into ZF to enable 100% PGP/MIME encryption.
// v0.11
// 2014-11-04 hollmeer: Protected function naming to _xxxx
// v0.12
// 2014-12-01 hollmeer: Changed all OpenGPG functionality configuration to use
// preferences
//
//
//
/////////////////////////////////////////////////////////////////////////////
//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
header("location: index.php");
exit;
}
class OpenPGPLib
{
//PGP/MIME HEADER CONSTANTS
const MULTIPART_PGP_ENCRYPTED = 'multipart/encrypted';
const TYPE_PGP_PROTOCOL = 'application/pgp-encrypted';
const PGP_MIME_NOTE = 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)';
const TYPE_PGP_CONTENT_VERSION = 'application/pgp-encrypted';
const DESCRIPTION_PGP_CONTENT_VERSION = 'PGP/MIME version identification';
const PGP_MIME_VERSION_IDENTIFICATION = 'Version: 1';
const TYPE_PGP_CONTENT_ENCRYPTED = 'application/octet-stream; name="encrypted.asc"';
const DESCRIPTION_PGP_CONTENT_ENCRYPTED = 'OpenPGP encrypted message';
const DISPOSITION_PGP_CONTENT_INLINE = 'inline; filename="encrypted.asc"';
/**
* EOL character string used by transport
* @var string
* @access public
*/
private $EOL = "\n";
/**
* Full path to gpg
* @var string
* @access protected
*/
private $_gpg_path;
/**
* Full path to keyring directory
* @var string
* @access protected
*/
private $_gpg_home;
/**
* gpg signer idfile
* @var string
* @access protected
*/
private $_gpg_sgn_id;
/**
* gpg signer passphrase
* @var string
* @access protected
*/
private $_gpg_sgn_passphrase;
/**
* gpg signer full passfile path
* @var string
* @access protected
*/
private $_gpg_sgn_passfile_path;
/**
* gpg trust
* depending on which version of GnuPG we're using there
* are two different ways to specify "always trust"
* @var string
* @access protected
*/
private $_gpg_trust;
/**
* Constructor function. Set initial defaults.
*/
public function __construct()
{
global $prefs,$tiki_p_admin;
$this->_gpg_path = $prefs['openpgp_gpg_path'];
$this->_gpg_home = $prefs['openpgp_gpg_home'];
$this->_gpg_sgn_id = $prefs['sender_email'];
if ($prefs['openpgp_gpg_signer_passphrase_store'] == 'file') {
$this->_gpg_sgn_passfile_path = $prefs['openpgp_gpg_signer_passfile'];
$this->_gpg_sgn_passphrase = '';
} else {
$this->_gpg_sgn_passfile_path = '';
$this->_gpg_sgn_passphrase = $prefs['openpgp_gpg_signer_passphrase'];
}
$this->_gpg_trust = '';
$this->setCrlf();
}
/**
* Accessor to set the CRLF style
*/
public function setCrlf($crlf = "\n")
{
if (! defined('CRLF')) {
define('CRLF', $crlf);
}
if (! defined('MAIL_MIMEPART_CRLF')) {
define('MAIL_MIMEPART_CRLF', $crlf);
}
}
/**
* Gnupg version check; sets internal variable once
*
* @access protected
* @return void
*/
protected function _gpg_check_version()
{
//////////////////////////////////////////
// find which version of GnuPG we're using
//////////////////////////////////////////
///////////////////////////////
// open the GnuPG process and get the reply
// we're only concerned with the first line of output, so use "false" as last argument
$commandline = $this->_gpg_path
. ' --version';
$ret = $this->_gpg_exec_proc($commandline, null, false);
/////////////////////////////////////////////////////
// get the version (we are only concerned with the first line of output,
// which was read from gpg-process-output as single-line-read into $ret[1]
$gpg_version_output = $ret[0];
///////////////////////////////////////////////
// sanity check - see if we're working with gpg
if (preg_match('/^gpg /', $gpg_version_output) == 0) {
$error_msg = 'gpg executable is not GnuPG: "' . $this->_gpg_path . '"';
trigger_error($error_msg, E_USER_ERROR);
// if an error message directs you to the line above please
// double check that your path to gpg is really GnuPG
die();
}
/////////////////////////////////////////////////////////////
// pick the version number out of $gpg_encrypt_version_output
// we'll need this so we can determine the correct
// way to tell GnuPG how to "always trust"
$gpg_gpg_version = preg_replace('/^.* /', '', $gpg_version_output);
////////////////////////////////////////////////////////
// depending on which version of GnuPG we're using there
// are two different ways to specify "always trust"
if ("$gpg_gpg_version" < '1.2.3') {
$this->_gpg_trust = '--always-trust'; // the old way
} else {
$this->_gpg_trust = '--trust-model always'; // the new way
}
/////////////////////////////////////////////
// unset variables that we don't need anymore
unset(
$gpg_version_output,
$gpg_gpg_version,
$commandline
);
////////////////////////////////////////
// we're done checking the GnuPG version
////////////////////////////////////////
return;
}
/**
* Gnupg process call function
*
* @param string $gpg_proc_call
* @param string $gpg_proc_input
* @param boolean $read_multilines
* @access protected
* @return array
* 0 => process call output (STDOUT)
* 1 => warnings and notices (STDERR)
* 2 => exit status
*/
protected function _gpg_exec_proc($gpg_proc_call = '', $gpg_proc_input = null, $read_multilines = true)
{
if ($gpg_proc_call == '') {
die;
}
//////////////////////////////////////////////
// set up pipes for handling I/O to/from GnuPG
$gpg_descriptorspec = [
0 => ["pipe", "r"], // STDIN is a pipe that GnuPG will read from
1 => ["pipe", "w"], // STDOUT is a pipe that GnuPG will write to
2 => ["pipe", "w"] // STDERR is a pipe that GnuPG will write to
];
///////////////////////////////
// this opens the GnuPG process
$gpg_process = proc_open(
$gpg_proc_call,
$gpg_descriptorspec,
$gpg_pipes
);
//////////////////////////////////////////////////////////////////
// this writes the "$gpg_encrypt_secret_message" to GnuPG on STDIN
if (is_resource($gpg_process)) {
if ($gpg_proc_input != null) {
fwrite($gpg_pipes[0], $gpg_proc_input);
}
fclose($gpg_pipes[0]);
/////////////////////////////////////////////////////////
// this reads the output from GnuPG from STDOUT
$gpg_proc_output = '';
if ($read_multilines) {
while (! feof($gpg_pipes[1])) {
$gpg_proc_output .= fgets($gpg_pipes[1], 1024);
}
fclose($gpg_pipes[1]);
} else {
$gpg_proc_output = fgets($gpg_pipes[1], 1024);
}
/////////////////////////////////////////////////////////
// this reads warnings and notices from GnuPG from STDERR
$gpg_error_message = '';
while (! feof($gpg_pipes[2])) {
$gpg_error_message .= fgets($gpg_pipes[2], 1024);
}
fclose($gpg_pipes[2]);
/////////////////////////////////////////
// this collects the exit status of GnuPG
$gpg_exit_status = proc_close($gpg_process);
////////////////////////////////////////////
// unset variables that are no longer needed
// and can only cause trouble
unset(
$gpg_descriptorspec,
$gpg_process,
$gpg_pipes
);
////////////////////////////////////
// this returns an array containing:
// [0] encrypted output (STDOUT)
// [1] warnings and notices (STDERR)
// [2] exit status
return [$gpg_proc_output, $gpg_error_message, $gpg_exit_status];
} else {
////////////////////////////////////////////
// unset variables that are no longer needed
// and can only cause trouble
unset(
$gpg_descriptorspec,
$gpg_process,
$gpg_pipes
);
//////////////////////////////
// set output as otherwise nothing
$gpg_proc_output = '';
$gpg_error_message = 'Fatal process call error: Process call failed!';
$gpg_exit_status = 99;
return [$gpg_proc_output, $gpg_error_message, $gpg_exit_status];
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Encryption function; encrypts & signs the message
//
// usage:
// array gpg_encrypt(secret-message, recipients);
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Encryption function; encrypts & signs the message
*
* @param string $secret-message
* @param array/string $recipients
* @access public
* @return array
* 0 => encrypted message output (STDOUT)
* 1 => warnings and notices (STDERR)
* 2 => exit status
*/
public function gpg_encrypt()
{
global $prefs;
//////////////////////////////////////////////////////////
// sanity check - make sure there are at least 2 arguments
// any extra arguments are considered to be additional key IDs
if (func_num_args() < 2) {
trigger_error("gpg_encrypt() requires at least 2 arguments", E_USER_ERROR);
// if an error message directs you to the line above please
// double check that you are providing at least 2 arguments
die();
}
////////////////////////////////
// assign arguments to variables
$gpg_args = func_get_args();
$gpg_secret_message = array_shift($gpg_args); // 1st argument - secret message; let the gpg process to deal with empty/faulty content
///////////////////////////////////////////////////////////////////////
// make sure that each recipient has the message encrypted to their key
// the 2nd argument, and any subsequent arguments, are key IDs
$gpg_recipient_list = '';
foreach ($gpg_args as $gpg_recipient) {
if (is_array($gpg_recipient)) {
foreach ($gpg_recipient as &$item) {
$gpg_recipient_list .= ' -r ' . $item;
}
} else {
$gpg_recipient_list .= " -r ${gpg_recipient}";
}
}
//////////////////////////////////////////
// find which version of GnuPG we're using
//////////////////////////////////////////
if ($this->_gpg_trust == '') {
$this->_gpg_check_version();
}
///////////////////////////////
// open the GnuPG process and get the reply
$commandline = '';
if ($prefs['openpgp_gpg_signer_passphrase_store'] == 'file') {
// get signer-key passphrase from a file
$commandline .= $this->_gpg_path
. ' --no-random-seed-file'
. ' --homedir ' . $this->_gpg_home
. ' ' . $this->_gpg_trust
. ' --batch'
. ' --local-user ' . $this->_gpg_sgn_id
. ' --passphrase-file ' . $this->_gpg_sgn_passfile_path
. ' -sea ' . $gpg_recipient_list
. ' ';
} else {
// get signer-key passphrase from preferences
$commandline .= $this->_gpg_path
. ' --no-random-seed-file'
. ' --homedir ' . $this->_gpg_home
. ' ' . $this->_gpg_trust
. ' --batch'
. ' --local-user ' . $this->_gpg_sgn_id
. ' --passphrase ' . $this->_gpg_sgn_passphrase
. ' -sea ' . $gpg_recipient_list
. ' ';
}
$ret = $this->_gpg_exec_proc($commandline, $gpg_secret_message);
unset(
$gpg_args,
$gpg_secret_message,
$gpg_recipient_list,
$commandline
);
////////////////////////////////////
// this returns an array containing:
// [0] encrypted output (STDOUT)
// [1] warnings and notices (STDERR)
// [2] exit status
return $ret;
}
/////////////////////////////////////////////////////////////
//
// Get the public-key fingerprint for a key associated with the ID
//
/////////////////////////////////////////////////////////////
/**
* Get public-key fingerprint
*
* @param string $gpg_key_id
* @access public
* @return array
* 0 => public key gingerprint output (STDOUT)
* 1 => warnings and notices (STDERR)
* 2 => exit status
*/
public function gpg_getFingerprint($gpg_key_id = null)
{
//////////////////////////////////////////////////////////
// sanity check - make sure there is 1 argument
if ($gpg_key_id == null) {
trigger_error("gpg_getFingerprint() requires 1 argument", E_USER_ERROR);
// if an error message directs you to the line above please
// double check that you are providing 1 argument
die();
}
///////////////////////////////////////////////////////////////////////
// the argument is key ID; if array, accept only the first
$gpg_key_id_to_return = '';
if (is_array($gpg_key_id)) {
foreach ($gpg_key_id as &$item) {
$gpg_key_id_to_return .= $item;
break;
}
} else {
$gpg_key_id_to_return .= $gpg_key_id;
}
//////////////////////////////////////////
// find which version of GnuPG we're using
//////////////////////////////////////////
if ($this->_gpg_trust == '') {
$this->_gpg_check_version();
}
///////////////////////////////
// open the GnuPG process and get the reply
$commandline = $this->_gpg_path
. ' --homedir ' . $this->_gpg_home
. ' ' . $this->_gpg_trust
. ' --fingerprint'
. ' --list-sigs ' . $gpg_key_id_to_return
. ' ';
$ret = $this->_gpg_exec_proc($commandline);
unset(
$gpg_key_id_to_return,
$commandline
);
////////////////////////////////////
// this returns an array containing:
// [0] fingerprint output (STDOUT)
// [1] warnings and notices (STDERR)
// [2] exit status
return $ret;
}
//////////////////////////////////////////////////////////////////////////////
//
// Get the public-key ascii-armor-block for a key associated with the ID
//
//////////////////////////////////////////////////////////////////////////////
/**
* Get public-key ascii armor block
*
* @param string $gpg_key_id
* @access public
* @return array
* 0 => public key armor output (STDOUT)
* 1 => warnings and notices (STDERR)
* 2 => exit status
*/
public function gpg_getPublicKey($gpg_key_id = null)
{
//////////////////////////////////////////////////////////
// sanity check - make sure there is 1 argument
if ($gpg_key_id == null) {
trigger_error("gpg_getPublicKey() requires 1 argument", E_USER_ERROR);
// if an error message directs you to the line above please
// double check that you are providing 1 argument
die();
}
///////////////////////////////////////////////////////////////////////
// the argument is key ID; if array, accept only the first
$gpg_key_id_to_return = '';
if (is_array($gpg_key_id)) {
foreach ($gpg_key_id as &$item) {
$gpg_key_id_to_return .= $item;
break;
}
} else {
$gpg_key_id_to_return .= $gpg_key_id;
}
//////////////////////////////////////////
// find which version of GnuPG we're using
//////////////////////////////////////////
if ($this->_gpg_trust == '') {
$this->_gpg_check_version();
}
///////////////////////////////
// open the GnuPG process and get the reply
$commandline = $this->_gpg_path
. ' --homedir ' . $this->_gpg_home
. ' ' . $this->_gpg_trust
. ' --export --armor ' . $gpg_key_id_to_return
. ' ';
$ret = $this->_gpg_exec_proc($commandline);
unset(
$gpg_key_id_to_return,
$commandline
);
////////////////////////////////////
// this returns an array containing:
// [0] public key armor output (STDOUT)
// [1] warnings and notices (STDERR)
// [2] exit status
return $ret;
}
/////////////////////////////////////////////////////////////////
//
// MESSAGE COMPOSING
//
/////////////////////////////////////////////////////////////////
/**
* ALPHAFIELDS 2012-01-08: added to retrieve admin email public key armor block
* Returns the contact users' email or empty
*
* @access public
* @return string (contact users' email or empty)
*/
public function get_admin_email_for_armor_block()
{
global $user, $prefs, $tikilib;
$empty = '';
return isset($prefs['sender_email']) ? $prefs['sender_email'] : $empty;
}
/////////////////////////////////////////////////////////////////////////////
/**
* Function to get publickey ascii-armor-block and original headers into body
*
* @param string $req_priority
* @param string $req_to
* @param string $req_cc
* @access public
* @return array
* 0 => $prepend_email_body
* 1 => $user_armor
*/
public function getPublickeyArmorBlock($req_priority, $req_to, $req_cc)
{
global $user;
$userlib = TikiLib::lib('user');
// get user email for publickey armor block retrieval
$user_email = '';
if ($user != 'admin') {
$user_email = $userlib->get_user_email($user);
} else {
// NOTE: This function is in this lib-class, not in $userlib!
$user_email = $this->get_admin_email_for_armor_block();
}
$user_armor = '';
if ($user_email) {
//retrieve armor block for keyid
$gpg = $this->gpg_getPublicKey($user_email);
// $gpg is an array containing
// $gpg[0] armor output (STDOUT)
// $gpg[1] warnings and notices (STDERR)
// $gpg[2] exit status from gpg
// test gpg's exit status
if ("$gpg[2]" == '0') {
// if the gpg command returned zero
$user_armor = "\n\n--original sender public key below--\n\n" . $gpg[0];
} else {
// if the gpg command returned non-zero
$error_msg = 'OpenPGPLib: _getPublickeyArmorBlock() returned error code: ' . $gpg[2];
trigger_error($error_msg, E_USER_ERROR);
// if an error message directs you to the line above please
// double check that your gnupg-configuration, process-call commandline input, and other parameters are correct
}
// TODO: hardcoded addresses into preferences from db
$gpg = $this->gpg_getFingerprint($user_email);
// $gpg is an array containing
// $gpg[0] fingerprint (STDOUT)
// $gpg[1] warnings and notices (STDERR)
// $gpg[2] exit status from gpg
// test gpg's exit status
if ("$gpg[2]" == '0') {
// if the gpg command returned zero
$user_armor = $user_armor . "\n-----fingerprint data below-------\n\n" . $gpg[0]
. "\n----------------------------------\n\n";
} else {
// if the gpg command returned non-zero
$error_msg = 'OpenPGPLib: _getPublickeyArmorBlock() returned error code: ' . $gpg[2];
trigger_error($error_msg, E_USER_ERROR);
// if an error message directs you to the line above please
// double check that your gnupg-configuration, process-call commandline input, and other parameters are correct
}
}
// generate a message has ID to be used as a consistent message referenceID across all recipients
// and prepend it into message body
$tmpstr = chunk_split(md5(rand() . microtime()), 8, '-');
$tmpstr = substr($tmpstr, 0, strlen($tmpstr) - 1);
$prepend_email_body = "ID: "
. $tmpstr
. " (use this for message reference)\n"
. "Prio: " . $req_priority . "\n"
. "From: " . $user . " (" . $user_email . ")\n"
. "To: " . $req_to . "\n"
. "Cc: " . $req_cc . "\n\n";
return [$prepend_email_body,$user_armor];
}
/////////////////////////////////////////////////////////////////
//
// WEBMAIL/htmlMimeMail GNUPG-ENCRYPTED PGP/MIME MAIL FUNCTIONS
//
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/**
* Function to encode a header if necessary
* according to RFC2047
*/
public function _encodeHeader($input, $charset = 'ISO-8859-1')
{
preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $input, $matches);
foreach ($matches[1] as $value) {
$replacement = preg_replace_callback('/([\\x80-\\xFF])/', function ($match) {
return "=" . strtoupper(dechex(ord($match[1])));
}, $value);
$input = str_replace($value, '=?' . $charset . '?Q?' . $replacement . '?=', $input);
}
return $input;
}
/////////////////////////////////////////////////////////////////
/**
* Send a message to a user with gpg-armor block etc included
* A changed encryption-related version was copied/changed from lib/messu/messulib.pgp
* into lib/openpgp/openpgplib.php for prepending/appending content into
* message body
* @param string $user
* @param string $from
* @param string $to
* @param string $cc
* @param string $subject
* @param string $body
* @param string $prepend_email_body
* @param string $user_pubkeyarmor
* @param string $priority
* @param string $replyto_hash
* @param string $replyto_email
* @param string $bcc_sender
* @access public
* @return boolean true/false
*/
public function post_message_with_pgparmor_attachment(
$user,
$from,
$to,
$cc,
$subject,
$body,
$prepend_email_body,
// NOTE this
$user_pubkeyarmor,
// NOTE this
$priority,
$replyto_hash = '',
$replyto_email = '',
$bcc_sender = ''
) {
global $prefs;
$userlib = TikiLib::lib('user');
$tikilib = TikiLib::lib('tiki');
$smarty = TikiLib::lib('smarty');
$subject = strip_tags($subject);
$body = strip_tags($body, '<a><b><img><i>');
// Prevent duplicates
$hash = md5($subject . $body);
if ($tikilib->getOne("select count(*) from `messu_messages` where `user`=? and `user_from`=? and `hash`=?", [$user,$from,$hash])) {
return false;
}
$query = "insert into `messu_messages`(`user`,`user_from`,`user_to`,`user_cc`,`subject`,`body`,`date`,`isRead`,`isReplied`,`isFlagged`,`priority`,`hash`,`replyto_hash`) values(?,?,?,?,?,?,?,?,?,?,?,?,?)";
$tikilib->query($query, [$user,$from,$to,$cc,$subject,$body,(int) $tikilib->now,'n','n','n',(int) $priority,$hash,$replyto_hash]);
// Now check if the user should be notified by email
$foo = parse_url($_SERVER["REQUEST_URI"]);
$machine = $tikilib->httpPrefix(true) . $foo["path"];
$machine = str_replace('messu-compose', 'messu-mailbox', $machine);
if ($tikilib->get_user_preference($user, 'minPrio', 6) <= $priority) {
if (! isset($_SERVER["SERVER_NAME"])) {
$_SERVER["SERVER_NAME"] = $_SERVER["HTTP_HOST"];
}
$email = $userlib->get_user_email($user);
if ($email) {
include_once('lib/webmail/tikimaillib.php');
$smarty->assign('mail_site', $_SERVER["SERVER_NAME"]);
$smarty->assign('mail_machine', $machine);
$smarty->assign('mail_date', $tikilib->now);
$smarty->assign('mail_user', stripslashes($user));
$smarty->assign('mail_from', stripslashes($from));
$smarty->assign('mail_subject', stripslashes($subject));
////////////////////////////////////////////////////////////////////////
// //
// ALPHAFIELDS 2012-11-03: ADDED PGP/MIME ENCRYPTION PREPARATION //
// USING lib/openpgp/opepgplib.php //
// //
// prepend original headers into email //
$aux_body = $prepend_email_body . $body;
$body = $aux_body;
// //
////////////////////////////////////////////////////////////////////////
$smarty->assign('mail_body', stripslashes($body));
$mail = new TikiMail($user);
$lg = $tikilib->get_user_preference($user, 'language', $prefs['site_language']);
if (empty($subject)) {
$s = $smarty->fetchLang($lg, 'mail/messu_message_notification_subject.tpl');
$mail->setSubject(sprintf($s, $_SERVER["SERVER_NAME"]));
} else {
$mail->setSubject($subject);
}
$mail_data = $smarty->fetchLang($lg, 'mail/messu_message_notification.tpl');
////////////////////////////////////////////////////////////////////////
// //
// ALPHAFIELDS 2012-11-03: ADDED PGP/MIME ENCRYPTION PREPARATION //
// USING lib/openpgp/opepgplib.php //
// //
// append pgparmor block and fingerprint into email //
$mail_data .= $user_pubkeyarmor;
// //
////////////////////////////////////////////////////////////////////////
$mail->setText($mail_data);
if ($userlib->user_exists($from)) {
$from_email = $userlib->get_user_email($from);
if ($bcc_sender === 'y' && ! empty($from_email)) {
$mail->setBcc($from_email);
}
if ($replyto_email !== 'y' && $userlib->get_user_preference($from, 'email is public', 'n') == 'n') {
$from_email = ''; // empty $from_email if not to be used - saves getting it twice
}
if (! empty($from_email)) {
$mail->setReplyTo($from_email);
}
}
if (! empty($from_email)) {
$mail->setFrom($from_email);
}
if (! $mail->send([$email], 'mail')) {
return false; //TODO echo $mail->errors;
}
}
}
return true;
}
/////////////////////////////////////////////////////////////////
/**
* Function to insert original subject into text part body
*
* @param array $mail_headers / headers array
* @param string $text / The text body of the message
* @access public
* @return string $text / The text body of the message prepended with original subject
*/
public function prependSubjectToText($mail_headers, $text)
{
// AS THE Subject-header is hidden and contains a hash only in a pgp/mime encrypted message subject-header
// extract the original subject and prepend it into the text part
// TODO: for some reason, newsletter notifications etc
// do not set Subject yet at this point so no adjustment is generated for them;
// what is the problem and solution?
$subject = '';
if (! empty($mail_headers['Subject'])) {
$subject = $mail_headers['Subject'];
}
$ret = "******** PGP/MIME-ENCRYPTED MESSAGE ********\n"
. "Subject: " . $subject
. "\n\n"
. $text;
return $ret;
}
/**
* Function to insert original subject into html part body
*
* @param array $mail_headers / headers array
* @param string $html / The html body of the message
* @param string $text / The text body of the message
* @access public
* @return array
* 0 => $html / The html body of the message prepended with original subject
* 1 => $html_text / The html_text body of the message prepended with original subject
*/
public function prependSubjectToHtml($mail_headers, $html, $text)
{
// AS THE Subject-header is hidden and contains a hash only in a pgp/mime encrypted message subject-header
// extract the original subject and prepend it into the html part
// TODO: for some reason, newsletter notifications etc
// do not set Subject yet at this point so no adjustment is generated for them;
// what is the problem and solution?
$subject = '';
if (! empty($mail_headers['Subject'])) {
$subject = $mail_headers['Subject'];
}
$ret_html = "******** PGP/MIME-ENCRYPTED MESSAGE ********<br>"
. "Subject: " . $subject
. "<br>"
. $html;
$ret_html_text = "******** PGP/MIME-ENCRYPTED MESSAGE ********<br>"
. "Subject: " . $subject
. "<br>"
. $text;
return [$ret_html,$ret_html_text];
}
/////////////////////////////////////////////////////////////////
/**
* Prepate encryption of a mail using gnupg
*
* @param array $original_mail_headers / headers array
* @param string $original_mail_body / The main body of the message after building
* @param string $mail_build_params_head_charset
* @param array/string $recipients (needs to be not-imploded string or array)
* @access public
* @return array
* 0 => string $gnupg_header
* 1 => string $gnupg_subject
* 2 => string $pgpmime_encrypted_message_body
*/
public function prepareEncryptWithMailSender($original_mail_headers, $original_mail_body, $mail_build_params_head_charset, $mail_recipients)
{
// Define gnupg/mime header variables; see constants above in this class
$gnupg_mpe = OpenPGPLib::MULTIPART_PGP_ENCRYPTED;
$gnupg_tpp = OpenPGPLib::TYPE_PGP_PROTOCOL;
$gnupg_pmn = OpenPGPLib::PGP_MIME_NOTE;
$gnupg_tpcv = OpenPGPLib::TYPE_PGP_CONTENT_VERSION;
$gnupg_dpcv = OpenPGPLib::DESCRIPTION_PGP_CONTENT_VERSION;
$gnupg_pmvi = OpenPGPLib::PGP_MIME_VERSION_IDENTIFICATION;
$gnupg_tpce = OpenPGPLib::TYPE_PGP_CONTENT_ENCRYPTED;
$gnupg_dpce = OpenPGPLib::DESCRIPTION_PGP_CONTENT_ENCRYPTED;
$gnupg_dpci = OpenPGPLib::DISPOSITION_PGP_CONTENT_INLINE;
// Define gnupg boundary
$gnupg_boundary = '------gnupg-' . md5(rand() . microtime());
// Get flat representation of headers
foreach ($original_mail_headers as $name => $value) {
// first process Content-Type and discard original and set pgp/mime-required after loop
if ($name == 'Content-Type') {
$tmpvalue = $this->_encodeHeader($value, $mail_build_params_head_charset);
// check if original message is just text/plain, so we can discard the entire
// orig_header from being prepended into encrypted part
$pos = strpos($tmpvalue, 'text/plain');
if ($pos !== false) {
// so text/plain ...set discard-flag...
$discard_orig_header = true;
} else {
// reach here if not text/plain
$orig_headers[] = $name . ': ' . $tmpvalue;
}
} else {
// Get flat representation of original headers
$orig_headers[] = $name . ': ' . $this->_encodeHeader($value, $mail_build_params_head_charset);
}
}
// save original header to be added into encrypted part
if ($discard_orig_header) {
// original body is text/plain, so no headers there
$orig_header = '';
} else {
$orig_header = implode(CRLF, $orig_headers);
}
//////////////////////////////////////////////////////////////////////////
// NOTE: pgpmime_header; this must be returned as header (string here) for the mail() function
$pgpmime_header = "Content-Type: {$gnupg_mpe};{$this->EOL} protocol=\"{$gnupg_tpp}\";{$this->EOL} boundary=\"{$gnupg_boundary}\"{$this->EOL}{$this->EOL}";
// Instead of original Subject, use this to hide even the subject:
// - generate a message hash ID to be used instead of orig subject;
// - show the original Subject in the encrypted message body;
$tmpstr = chunk_split(
md5(
$pgpmime_header
. rand()
. microtime()
),
8,
'-'
);
$tmpstr = substr($tmpstr, 0, strlen($tmpstr) - 1);
$replace_subject_with_msgID = '[PGP/MIME] ' . $tmpstr;
//////////////////////////////////////////////////////////////////////////
// NOTE: pgpmime subject; this must be returned as subject for the mail() function
$pgpmime_subject = $replace_subject_with_msgID;
// gnupg pgp/mime note
$gnupg = "{$gnupg_pmn}{$this->EOL}";
// gnupg part 1 header
$gnupg .= "--{$gnupg_boundary}{$this->EOL}Content-Type: {$gnupg_tpcv}{$this->EOL}Content-Description: {$gnupg_dpcv}{$this->EOL}{$this->EOL}{$gnupg_pmvi}{$this->EOL}{$this->EOL}";
// gnupg part 2 header
$gnupg .= "--{$gnupg_boundary}{$this->EOL}Content-Type: {$gnupg_tpce}{$this->EOL}Content-Description: {$gnupg_dpce}{$this->EOL}Content-Disposition: {$gnupg_dpci}{$this->EOL}{$this->EOL}";
// gnupg encrypted/signed message body
// original header to be added into encrypted part (use here the prepared/imploded orig_header from above)
// NOTE: signer and signer passphrase are set ready in this class instantiation,
// and used in the following function directly from there
$gnupg .= $this->_encryptSignGnuPG($orig_header . "{$this->EOL}{$this->EOL}" . $original_mail_body, $mail_recipients);
// gnupg end boundary
$gnupg .= "{$this->EOL}--{$gnupg_boundary}--{$this->EOL}";
//////////////////////////////////////////////////////////////////////////
// NOTE: gnupg into mail body; this must be returned as encrypted body for the mail() function
$pgpmime_encrypted_message_body = $gnupg;
return [
0 => $pgpmime_header,
1 => $pgpmime_subject,
2 => $pgpmime_encrypted_message_body];
}
/////////////////////////////////////////////////////////////////
/**
* Prepare encryption of a mail using gnupg
*
* @param array $original_mail_headers / headers array
* @param string $original_mail_body / The main body of the message after building
* @param string $mail_build_params_head_charset
* @param array/string $recipients (needs to be not-imploded string or array)
* @access public
* @return array
* 0 => array $gnupg_header_array
* 1 => string $pgpmime_encrypted_message_body
*/
public function prepareEncryptWithSmtpSender($original_mail_headers, $original_mail_body, $mail_build_params_head_charset, $mail_recipients)
{
// Define gnupg/mime header variables; see constants above in this class
$gnupg_mpe = OpenPGPLib::MULTIPART_PGP_ENCRYPTED;
$gnupg_tpp = OpenPGPLib::TYPE_PGP_PROTOCOL;
$gnupg_pmn = OpenPGPLib::PGP_MIME_NOTE;
$gnupg_tpcv = OpenPGPLib::TYPE_PGP_CONTENT_VERSION;
$gnupg_dpcv = OpenPGPLib::DESCRIPTION_PGP_CONTENT_VERSION;
$gnupg_pmvi = OpenPGPLib::PGP_MIME_VERSION_IDENTIFICATION;
$gnupg_tpce = OpenPGPLib::TYPE_PGP_CONTENT_ENCRYPTED;
$gnupg_dpce = OpenPGPLib::DESCRIPTION_PGP_CONTENT_ENCRYPTED;
$gnupg_dpci = OpenPGPLib::DISPOSITION_PGP_CONTENT_INLINE;
// Define gnupg boundary
$gnupg_boundary = '------gnupg-' . md5(rand() . microtime());
// Define a boundary for orig body, if needed
$add_boundary_for_orig = '------orig--' . md5(rand() . microtime());
// set discard-flag originally false; see below foreach loop and later the origheader implode
$discard_orig_header = true;
// Get flat representation of original headers to be put with the encrypted body
foreach ($original_mail_headers as $name => $value) {
// first process Content-Type and discard original and set pgp/mime-required after loop
if ($name == 'Content-Type') {
$tmpvalue = $this->_encodeHeader($value, $mail_build_params_head_charset);
// check if original message is just text/plain, so we can discard the entire
// orig_header from being prepended into encrypted part
$pos = strpos($tmpvalue, 'text/plain');
if ($pos !== false) {
// so text/plain ...set discard-flag...
$discard_orig_header = true;
} else {
// reach here if not text/plain
$orig_headers[] = $name . ': ' . $tmpvalue;
}
} else {
// Get flat representation of original headers
$orig_headers[] = $name . ': ' . $this->_encodeHeader($value, $mail_build_params_head_charset);
}
}
// save original header to be added into encrypted part
if ($discard_orig_header) {
// original body is text/plain, so no headers there
$orig_header = '';
} else {
$orig_header = implode(CRLF, $orig_headers);
}
// gnupg pgp/mime headers; original headers from call-parameters to be added into encrypted part (see above/below)
$pgpmime_header = "Content-Type: {$gnupg_mpe};{$this->EOL} protocol=\"{$gnupg_tpp}\";{$this->EOL} boundary=\"{$gnupg_boundary}\"{$this->EOL}{$this->EOL}";
//////////////////////////////////////////////////////////////////////////
// Instead of original Subject, use this to hide even the subject:
// - generate a message hash ID to be used instead of orig subject;
// - show the original Subject in the encrypted message body;
$tmpstr = chunk_split(
md5(
$pgpmime_header
. rand()
. microtime()
),
8,
'-'
);
$tmpstr = substr($tmpstr, 0, strlen($tmpstr) - 1);
$replace_subject_with_msgID = '[PGP/MIME] ' . $tmpstr;
//////////////////////////////////////////////////////////////////////////
// NOTE: pgpmime_header_array; this must be returned as header array (NOTE: array here) for the mail() function
// prepend the opaque Subject before other PGP/MIME headers
$pgpmime_header_array[] = "Subject: " . $replace_subject_with_msgID;
// instead of the original, set this pgp/mime-required header into $gnupg_header_array -return array
$pgpmime_header_array[] = $pgpmime_header;
// Create gnupg pgp/mime parts etc below and set/repalced to message body by caller
// NOTE: here we do not need to create directly the $gpgpmime_header, because the $gnupg_header_array is returned back
// to caller in array format (caller is htmlMimeMail)
// gnupg pgp/mime note
$gnupg = "{$gnupg_pmn}{$this->EOL}";
// gnupg part 1 header
$gnupg .= "--{$gnupg_boundary}{$this->EOL}Content-Type: {$gnupg_tpcv}{$this->EOL}Content-Description: {$gnupg_dpcv}{$this->EOL}{$this->EOL}{$gnupg_pmvi}{$this->EOL}{$this->EOL}";
// gnupg part 2 header
$gnupg .= "--{$gnupg_boundary}{$this->EOL}Content-Type: {$gnupg_tpce}{$this->EOL}Content-Description: {$gnupg_dpce}{$this->EOL}Content-Disposition: {$gnupg_dpci}{$this->EOL}{$this->EOL}";
// gnupg encrypted/signed message body
// original header to be added into encrypted part (use here the prepared/imploded orig_header from above)
// NOTE: signer and signer passphrase are set ready in this class instantiation,
// and used in the following function directly from there
$gnupg .= $this->_encryptSignGnuPG($orig_header . "{$this->EOL}{$this->EOL}" . $original_mail_body, $mail_recipients);
// gnupg end boundary
$gnupg .= "{$this->EOL}--{$gnupg_boundary}--{$this->EOL}";
//////////////////////////////////////////////////////////////////////////
// NOTE: gnupg into mail body; this must be returned as encrypted body for the mail() function
$pgpmime_encrypted_message_body = $gnupg;
return [
0 => $pgpmime_header_array,
1 => $pgpmime_encrypted_message_body];
}
//////////////////////////////////////////////////////////
//
// ZEND FRAMEWORK GNUPG-ENCRYPTED PGP/MIME MAIL FUNCTIONS
//
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
/**
* Prepend header name to header value; copied here from Laminas/Mail/Transport/Abstract
* See usage in the array_walk in function below
* @param string $item
* @param string $key
* @param string $prefix
* @static
* @access protected
* @return void
*/
protected static function _formatHeader(&$item, $key, $prefix)
{
$item = $prefix . ': ' . $item;
}
//////////////////////////////////////////////////////////
/**
* Prepate encryption of a mail using gnupg
*
* @param string $original_mail_headers
* @param string $original_mail_body
* @param array|string $recipients
* @access public
* @return array
* 0 => string $pgpmime_header
* 1 => string $pgpmime_encrypted_message_body
*/
public function prepareEncryptWithZendMail($original_mail_header, $original_mail_body, $recipients)
{
// Define gnupg/mime header variables; see constants above in this class
$gnupg_mpe = OpenPGPLib::MULTIPART_PGP_ENCRYPTED;
$gnupg_tpp = OpenPGPLib::TYPE_PGP_PROTOCOL;
$gnupg_pmn = OpenPGPLib::PGP_MIME_NOTE;
$gnupg_tpcv = OpenPGPLib::TYPE_PGP_CONTENT_VERSION;
$gnupg_dpcv = OpenPGPLib::DESCRIPTION_PGP_CONTENT_VERSION;
$gnupg_pmvi = OpenPGPLib::PGP_MIME_VERSION_IDENTIFICATION;
$gnupg_tpce = OpenPGPLib::TYPE_PGP_CONTENT_ENCRYPTED;
$gnupg_dpce = OpenPGPLib::DESCRIPTION_PGP_CONTENT_ENCRYPTED;
$gnupg_dpci = OpenPGPLib::DISPOSITION_PGP_CONTENT_INLINE;
// Define gnupg boundary
$gnupg_boundary = '------gnupg-' . md5(rand() . microtime());
//*********************
// NOTE: in smtp, $mail_recipients includes the Cc and Bcc recipients.
// so the message gets properly encrypted for them also;
// the problem is still with the Sendmail for such cases / see above...?
// What is the case in Zend Abstract, Sendmail and Smtp...?
//*********************
// Create gnupg pgp/mime parts etc below and set to this->output
// gnupg pgp/mime headers; original headers from call-parameters to be added into encrypted part (see below)
$pgpmime_header = "Content-Type: {$gnupg_mpe};{$this->EOL} protocol=\"{$gnupg_tpp}\";{$this->EOL} boundary=\"{$gnupg_boundary}\"{$this->EOL}{$this->EOL}";
// Instead of original Subject, use this to hide even the subject:
// - generate a message hash ID to be used instead of orig subject;
// - show the original Subject in the encrypted message body;
$tmpstr = chunk_split(
md5(
$pgpmime_header
. rand()
. microtime()
),
8,
'-'
);
$tmpstr = substr($tmpstr, 0, strlen($tmpstr) - 1);
$replace_subject_with_msgID = '[PGP/MIME] ' . $tmpstr;
// prepend the opaque Subject before other PGP/MIME headers
$pgpmime_header = "Subject: " . $replace_subject_with_msgID . $this->EOL . $pgpmime_header;
// gnupg pgp/mime note
$gnupg = "{$gnupg_pmn}{$this->EOL}";
// gnupg part 1 header
$gnupg .= "--{$gnupg_boundary}{$this->EOL}Content-Type: {$gnupg_tpcv}{$this->EOL}Content-Description: {$gnupg_dpcv}{$this->EOL}{$this->EOL}{$gnupg_pmvi}{$this->EOL}{$this->EOL}";
// gnupg part 2 header
$gnupg .= "--{$gnupg_boundary}{$this->EOL}Content-Type: {$gnupg_tpce}{$this->EOL}Content-Description: {$gnupg_dpce}{$this->EOL}Content-Disposition: {$gnupg_dpci}{$this->EOL}{$this->EOL}";
// gnupg encrypted/signed message body
// original header to be added into encrypted part
// NOTE: signer and signer passphrase are set ready in this class instantiation,
// and used in the following function directly from there
$gnupg .= $this->_encryptSignGnuPG($original_mail_header . "{$this->EOL}{$this->EOL}" . $original_mail_body, $recipients);
// gnupg end boundary
$gnupg .= "{$this->EOL}--{$gnupg_boundary}--{$this->EOL}";
// gnupg into mail body
$pgpmime_encrypted_message_body = $gnupg;
return [
0 => $pgpmime_header,
1 => $pgpmime_encrypted_message_body];
}
//////////////////////////////////////////////////////////
/**
* Encrypt and sign a mail using gnupg
*
* @param string $unencrypted_message
* @param array/string $recipients (needs to be not-imploded string or array)
* @access protected
* @return string ($encrypted_message)
*/
protected function _encryptSignGnuPG($unencrypted_message, $recipients)
{
$encrypted_message = '';
// encrypt $message to recipients
// sign wth signer
$gpg = $this->gpg_encrypt("${unencrypted_message}", $recipients);
// $gpg is an array containing
// $gpg[0] encrypted output (STDOUT)
// $gpg[1] warnings and notices (STDERR)
// $gpg[2] exit status from gpg
// test gpg's exit status
if ("$gpg[2]" == '0') {
// if the gpg command returned zero
$encrypted_message = $gpg[0];
} else {
// if the gpg command returned non-zero
$error_msg = 'OpenPGPLib: _encryptSignGnuPG() returned error code: ' . $gpg[2];
trigger_error($error_msg, E_USER_ERROR);
// if an error message directs you to the line above please
// double check that your gnupg-configuration, process-call commandline input, and other parameters are correct
}
return $encrypted_message;
}
}
$openpgplib = new OpenPGPLib();