File: //usr/local/CyberCP/public/snappymail/snappymail/v/2.38.2/app/libraries/snappymail/gpg/base.php
<?php
/**
* GnuPG
*/
namespace SnappyMail\GPG;
use SnappyMail\SensitiveString;
abstract class Base
{
const
// @see https://pear.php.net/bugs/bug.php?id=21077
CHUNK_SIZE = 65536,
// This is used to pass data to the GPG process.
FD_INPUT = 0,
// This is used to receive normal output from the GPG process.
FD_OUTPUT = 1,
// This is used to receive error output from the GPG process.
FD_ERROR = 2,
/**
* GPG status output file descriptor. The status file descriptor outputs
* detailed information for many GPG commands. See the second section of
* the file <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG package} for a detailed
* description of GPG's status output.
*/
FD_STATUS = 3,
// This is used for methods requiring passphrases.
FD_COMMAND = 4,
// This is used for passing signed data when verifying a detached signature.
FD_MESSAGE = 5;
public
$strict = false;
protected
$binary,
$version = '2.0',
$pinentries = [],
$encryptKeys = [],
// Create PEM encoded output
$armor = true,
$_input,
$_output,
$proc_resource,
$_openPipes, // ProcPipes
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
$options = [
'homedir' => '',
'keyring' => '',
'digest-algo' => '',
'cipher-algo' => '',
];
function __construct(string $homedir)
{
$homedir = \rtrim($homedir, '/\\');
// BSD 4.4 max length
if (104 <= \strlen($homedir . '/S.gpg-agent.extra')) {
throw new \Exception('Socket name for S.gpg-agent.extra is too long');
}
$this->options['homedir'] = $homedir;
// \putenv("GNUPGHOME={$homedir}");
}
function __destruct()
{
$this->proc_close();
$gpgconf = static::findBinary('gpgconf');
if ($gpgconf) {
$cmd = $gpgconf . ' --kill gpg-agent';
// https://github.com/the-djmaze/snappymail/issues/1560#issuecomment-2144817883
// if (\version_compare($this->version, '2.4.0', '<')) {
$cmd .= ' ' . \escapeshellarg($this->options['homedir']);
// }
$env = ['GNUPGHOME' => $this->options['homedir']];
$pipes = [];
if ($process = \proc_open($cmd, [], $pipes, null, $env)) {
\proc_close($process);
}
}
}
public static function isSupported() : bool
{
return \is_callable('shell_exec') && \is_callable('proc_open');
}
protected function listDecryptKeys(/*string|resource*/ $input, /*string|resource*/ $output = null)
{
return false;
}
/**
* Decrypts a given text
*/
public function decrypt(string $text) /*: string|false */
{
return false;
}
/**
* Decrypts a given file
*/
public function decryptFile(string $filename) /*: string|false */
{
return false;
}
/**
* Decrypts a given stream
*/
public function decryptStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Decrypts and verifies a given text
*/
public function decryptVerify(string $text, string &$plaintext) /*: array|false*/
{
return false;
}
/**
* Decrypts and verifies a given file
*/
public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/
{
return false;
}
/**
* Encrypts a given text
*/
public function encrypt(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Encrypts a given text
*/
public function encryptFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Encrypts and signs a given text
*/
public function encryptSign(string $plaintext) /*: string|false*/
{
return false;
}
/**
* Encrypts and signs a given text
*/
public function encryptSignFile(string $filename) /*: string|false*/
{
return false;
}
/**
* Exports a public or private key
*/
public function export(string $fingerprint, ?SensitiveString $passphrase = null) /*: string|false*/
{
return false;
}
/**
* Imports a key
*/
public function import(string $keydata) /*: array|false*/
{
return false;
}
/**
* Imports a key
*/
public function importFile(string $filename) /*: array|false*/
{
return false;
}
public function deleteKey(string $keyId, bool $private)
{
return false;
}
/**
* Returns an array with information about all keys that matches the given pattern
*/
public function keyInfo(string $pattern, bool $private = false) : array
{
return [];
}
/**
* Sets the mode for error_reporting
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
* By default GNUPG_ERROR_SILENT is used.
*/
public function setErrorMode(int $errormode) : void
{
}
/**
* Signs a given text
*/
public function sign(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Signs a given file
*/
public function signFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Signs a given file
*/
public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Verifies a signed text
*/
public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/
{
return false;
}
/**
* Verifies a signed file
*/
public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/
{
return false;
}
/**
* Verifies a signed file
*/
public function verifyStream($fp, string $signature, string &$plaintext = null) /*: array|false*/
{
return false;
}
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
{
return [];
}
/******************************************************************
* Defined methods
******************************************************************/
/**
* Add a key for decryption
*/
public function addDecryptKey(string $fingerprint, SensitiveString $passphrase) : bool
{
$this->addPinentry($fingerprint, $passphrase);
return true;
}
/**
* Add a key for encryption
*/
public function addEncryptKey(string $fingerprint) : bool
{
$this->encryptKeys[$fingerprint] = 1;
return true;
}
/**
* Add a key for signing
*/
public function addSignKey(string $fingerprint, SensitiveString $passphrase) : bool
{
$this->addPinentry($fingerprint, $passphrase);
return true;
}
public function clear() : bool
{
$this->clearPinentries();
$this->clearEncryptKeys();
return true;
}
/**
* Removes all keys which were set for decryption before
*/
public function clearDecryptKeys() : bool
{
return $this->clearPinentries();
}
/**
* Removes all keys which were set for encryption before
*/
public function clearEncryptKeys() : bool
{
$this->encryptKeys = [];
return true;
}
/**
* Removes all keys which were set for signing before
*/
public function clearSignKeys() : bool
{
return $this->clearPinentries();
}
/**
* Returns the engine info
*/
public function getEngineInfo() : array
{
return [
'protocol' => null,
'file_name' => $this->binary,
'home_dir' => $this->options['homedir'],
'version' => $this->version
];
}
/**
* Add private key passphrase for decrypt, sign or export
* $keyId or fingerprint
*/
public function addPinentry(string $keyId, SensitiveString $passphrase)
{
/**
* Test first?
* gpg --dry-run --passwd <your-user-id>
$_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', [$keyId => $passphrase]));
$result = $this->exec([
'--dry-run',
'--passwd',
$passphrase
]);
*/
// $this->export($keyId, $passphrase);
$this->pinentries[$keyId] = $passphrase;
// $this->pinentries[\substr($keyId, -16)] = $passphrase;
return $this;
}
/**
* Removes all keys which were set for decryption, signing and export
*/
public function clearPinentries() : bool
{
$this->pinentries = [];
return true;
}
/**
* Toggle the armored output
* When true the output is ASCII
*/
public function setArmor(bool $armor = true) : bool
{
$this->armor = $armor;
return true;
}
protected function _debug(string $msg) : void
{
\SnappyMail\Log::debug('GPG', $msg);
}
protected function setInput(&$input) : void
{
if (\is_resource($input)) {
// https://github.com/the-djmaze/snappymail/issues/331
// $meta['stream_type'] == MEMORY or $meta['wrapper_data'] == MailSo\Base\StreamWrappers\Literal
$meta = \stream_get_meta_data($input);
if (!\in_array($meta['stream_type'], ['STDIO', 'TEMP'])) {
/*
$fp = \fopen('php://temp');
\stream_copy_to_stream($input, $fp);
$input = $fp;
*/
$input = \stream_get_contents($input);
}
}
$this->_input =& $input;
}
protected function setOutput($output)/* : resource|false*/
{
$fclose = false;
if ($output && !\is_resource($output)) {
$output = \fopen($output, 'wb');
if (!$output) {
throw new \Exception("Could not open file '{$filename}'");
}
$fclose = $output;
}
$this->_output = $output;
return $fclose;
}
public function agent()
{
// $home = \escapeshellarg($this->options['homedir']);
// echo `gpg-agent --daemon --homedir $home 2>&1`;
}
protected function getPassphrase($key)
{
$passphrase = '';
$keyIdLength = \strlen($key);
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = \json_decode($_ENV['PINENTRY_USER_DATA'], true);
foreach ($passphrases as $keyId => $pass) {
$length = \min($keyIdLength, \strlen($keyId));
if (\substr($keyId, -$length) === \substr($key, -$length)) {
return $pass;
}
}
}
// throw new \Exception("Passphrase not found for {$key}");
return '';
}
protected function proc_close() : int
{
$exitCode = 0;
// clear PINs from environment if they were set
$_ENV['PINENTRY_USER_DATA'] = null;
if (\is_resource($this->proc_resource)) {
$this->_debug('CLOSING SUBPROCESS');
// close remaining open pipes
$this->_openPipes->closeAll();
$status = \proc_get_status($this->proc_resource);
$exitCode = \proc_close($this->proc_resource);
$this->proc_resource = null;
// proc_close() can return -1 in some cases,
// get the real exit code from the process status
if ($exitCode < 0 && $status && !$status['running']) {
$exitCode = $status['exitcode'];
}
if ($exitCode > 0) {
$this->_debug('=> subprocess returned an unexpected exit code: ' . $exitCode);
}
}
return $exitCode;
}
protected static function findBinary($name) : ?string
{
$binary = \function_exists('shell_exec') ? \trim((string) `which $name`) : '';
if ($binary && \RainLoop\Utils::inOpenBasedir($binary) && \is_executable($binary)) {
return $binary;
}
$locations = \array_filter([
'/sw/bin/',
'/usr/bin/',
'/usr/local/bin/',
'/opt/local/bin/',
'/run/current-system/sw/bin/'
], '\RainLoop\Utils::inOpenBasedir');
foreach ($locations as $location) {
if (\is_executable($location . $name)) {
return $location . $name;
}
}
return null;
}
}