mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Added draft code to handle S/MIME through GnuPG #259
This commit is contained in:
parent
9cceafcb3e
commit
9933a0e83e
8 changed files with 1695 additions and 372 deletions
|
|
@ -14,6 +14,7 @@ namespace MailSo\Mail;
|
||||||
use MailSo\Base\Utils;
|
use MailSo\Base\Utils;
|
||||||
use MailSo\Imap\Enumerations\FetchType;
|
use MailSo\Imap\Enumerations\FetchType;
|
||||||
use MailSo\Mime\Enumerations\Header as MimeHeader;
|
use MailSo\Mime\Enumerations\Header as MimeHeader;
|
||||||
|
use SnappyMail\GPG\PGP as GPG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @category MailSo
|
* @category MailSo
|
||||||
|
|
@ -379,8 +380,8 @@ class Message implements \JsonSerializable
|
||||||
|
|
||||||
if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----')) {
|
if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----')) {
|
||||||
$keyIds = [];
|
$keyIds = [];
|
||||||
if (\SnappyMail\PGP\GPG::isSupported()) {
|
if (GPG::isSupported()) {
|
||||||
$GPG = new \SnappyMail\PGP\GPG('');
|
$GPG = new GPG('');
|
||||||
$keyIds = $GPG->getEncryptedMessageKeys($sText);
|
$keyIds = $GPG->getEncryptedMessageKeys($sText);
|
||||||
}
|
}
|
||||||
$oMessage->pgpEncrypted = [
|
$oMessage->pgpEncrypted = [
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
namespace RainLoop\Actions;
|
namespace RainLoop\Actions;
|
||||||
|
|
||||||
use SnappyMail\PGP\Backup;
|
use SnappyMail\PGP\Backup;
|
||||||
|
use SnappyMail\PGP\Keyservers;
|
||||||
|
use SnappyMail\PGP\GnuPG;
|
||||||
|
|
||||||
trait Pgp
|
trait Pgp
|
||||||
{
|
{
|
||||||
|
|
@ -14,7 +16,7 @@ trait Pgp
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
$keys = \SnappyMail\PGP\Backup::getKeys();
|
$keys = Backup::getKeys();
|
||||||
foreach ($keys['public'] as $key) {
|
foreach ($keys['public'] as $key) {
|
||||||
$result[] = $key['value'];
|
$result[] = $key['value'];
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +40,7 @@ trait Pgp
|
||||||
|
|
||||||
public function DoSearchPGPKey() : array
|
public function DoSearchPGPKey() : array
|
||||||
{
|
{
|
||||||
$result = \SnappyMail\PGP\Keyservers::get(
|
$result = Keyservers::get(
|
||||||
$this->GetActionParam('query', '')
|
$this->GetActionParam('query', '')
|
||||||
);
|
);
|
||||||
return $this->DefaultResponse($result ?: false);
|
return $this->DefaultResponse($result ?: false);
|
||||||
|
|
@ -47,7 +49,7 @@ trait Pgp
|
||||||
/**
|
/**
|
||||||
* @throws \MailSo\RuntimeException
|
* @throws \MailSo\RuntimeException
|
||||||
*/
|
*/
|
||||||
public function GnuPG() : ?\SnappyMail\PGP\GnuPG
|
public function GnuPG() : ?GnuPG
|
||||||
{
|
{
|
||||||
$oAccount = $this->getMainAccountFromToken();
|
$oAccount = $this->getMainAccountFromToken();
|
||||||
if (!$oAccount) {
|
if (!$oAccount) {
|
||||||
|
|
@ -103,7 +105,7 @@ trait Pgp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return \SnappyMail\PGP\GnuPG::getInstance($homedir);
|
return GnuPG::getInstance($homedir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function DoGnupgDecrypt() : array
|
public function DoGnupgDecrypt() : array
|
||||||
|
|
@ -203,14 +205,14 @@ trait Pgp
|
||||||
$sEmail = $aMatch[0];
|
$sEmail = $aMatch[0];
|
||||||
}
|
}
|
||||||
if ($sEmail) {
|
if ($sEmail) {
|
||||||
$aKeys = \SnappyMail\PGP\Keyservers::index($sEmail);
|
$aKeys = Keyservers::index($sEmail);
|
||||||
if ($aKeys) {
|
if ($aKeys) {
|
||||||
$sKeyId = $aKeys[0]['keyid'];
|
$sKeyId = $aKeys[0]['keyid'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($sKeyId) {
|
if ($sKeyId) {
|
||||||
$sKey = \SnappyMail\PGP\Keyservers::get($sKeyId);
|
$sKey = Keyservers::get($sKeyId);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
|
@ -237,7 +239,7 @@ trait Pgp
|
||||||
*/
|
*/
|
||||||
public function DoGetStoredPGPKeys() : array
|
public function DoGetStoredPGPKeys() : array
|
||||||
{
|
{
|
||||||
return $this->DefaultResponse(\SnappyMail\PGP\Backup::getKeys());
|
return $this->DefaultResponse(Backup::getKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
489
snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php
Normal file
489
snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php
Normal file
|
|
@ -0,0 +1,489 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GnuPG
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SnappyMail\GPG;
|
||||||
|
|
||||||
|
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',
|
||||||
|
$passphrases = [],
|
||||||
|
$signKeys = [],
|
||||||
|
$encryptKeys = [],
|
||||||
|
$decryptKeys = [],
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
$this->options['homedir'] = \rtrim($homedir, '/\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
function __destruct()
|
||||||
|
{
|
||||||
|
$this->proc_close();
|
||||||
|
|
||||||
|
$gpgconf = static::findBinary('gpgconf');
|
||||||
|
if ($gpgconf) {
|
||||||
|
$env = ['GNUPGHOME' => $this->options['homedir']];
|
||||||
|
$pipes = [];
|
||||||
|
if ($process = \proc_open($gpgconf . ' --kill gpg-agent --homedir ' . \escapeshellarg($this->options['homedir']), [], $pipes, null, $env)) {
|
||||||
|
\proc_close($process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isSupported() : bool
|
||||||
|
{
|
||||||
|
return \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 key
|
||||||
|
*/
|
||||||
|
public function export(string $fingerprint) /*: string|false*/
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports a private key
|
||||||
|
*/
|
||||||
|
public function exportPrivateKey(string $fingerprint) /*: 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, int $private = 0) : 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) /*: array|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,
|
||||||
|
#[\SensitiveParameter]
|
||||||
|
string $passphrase
|
||||||
|
) : bool
|
||||||
|
{
|
||||||
|
$this->decryptKeys[$fingerprint] = $passphrase;
|
||||||
|
// $this->decryptKeys[\substr($fingerprint, -16)] = $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,
|
||||||
|
#[\SensitiveParameter]
|
||||||
|
string $passphrase
|
||||||
|
) : bool
|
||||||
|
{
|
||||||
|
$this->signKeys[$fingerprint] = $passphrase;
|
||||||
|
// $this->signKeys[\substr($fingerprint, -16)] = $passphrase;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all keys which were set for decryption before
|
||||||
|
*/
|
||||||
|
public function clearDecryptKeys() : bool
|
||||||
|
{
|
||||||
|
$this->decryptKeys = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
$this->signKeys = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the engine info
|
||||||
|
*/
|
||||||
|
public function getEngineInfo() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'protocol' => null,
|
||||||
|
'file_name' => $this->binary,
|
||||||
|
'home_dir' => $this->options['homedir'],
|
||||||
|
'version' => $this->version
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addPassphrase($keyId,
|
||||||
|
#[\SensitiveParameter]
|
||||||
|
$passphrase
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->passphrases[$keyId] = $passphrase;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the armored output
|
||||||
|
*/
|
||||||
|
public function setArmor(int $armor = 1) : 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 = \trim((string) `which $name`);
|
||||||
|
if ($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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -5,74 +5,29 @@
|
||||||
* It does not support gpg < v2.2.5 as they are from before 2018
|
* It does not support gpg < v2.2.5 as they are from before 2018
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace SnappyMail\PGP;
|
namespace SnappyMail\GPG;
|
||||||
|
|
||||||
class GPG
|
class PGP extends 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;
|
|
||||||
|
|
||||||
private
|
private
|
||||||
$_message,
|
$_message,
|
||||||
$_input,
|
|
||||||
$_output,
|
|
||||||
|
|
||||||
$signKeys = [],
|
|
||||||
$encryptKeys = [],
|
|
||||||
$decryptKeys = [];
|
|
||||||
|
|
||||||
private
|
|
||||||
$binary,
|
|
||||||
$version = '2.0',
|
|
||||||
$ciphers = [],
|
$ciphers = [],
|
||||||
$digests = [],
|
$digests = [],
|
||||||
$curves = [],
|
$curves = [],
|
||||||
$pubkey_types = [],
|
$pubkey_types = [],
|
||||||
$compressions = [],
|
$compressions = [],
|
||||||
$passphrases = [],
|
|
||||||
|
|
||||||
$proc_resource,
|
$signmode = 2;
|
||||||
$_openPipes, // GpgProcPipes
|
|
||||||
|
|
||||||
$armor = true,
|
|
||||||
|
|
||||||
$signmode = 2,
|
|
||||||
|
|
||||||
|
protected
|
||||||
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
|
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
|
||||||
$options = [
|
$options = [
|
||||||
'homedir' => '',
|
'homedir' => '',
|
||||||
'keyring' => '',
|
'keyring' => '',
|
||||||
'secret-keyring' => '',
|
|
||||||
'digest-algo' => '',
|
'digest-algo' => '',
|
||||||
'cipher-algo' => '',
|
'cipher-algo' => '',
|
||||||
|
'secret-keyring' => '',
|
||||||
/*
|
/*
|
||||||
2 = ZLIB (GnuPG, default)
|
2 = ZLIB (GnuPG, default)
|
||||||
1 = ZIP (PGP)
|
1 = ZIP (PGP)
|
||||||
|
|
@ -83,7 +38,7 @@ class GPG
|
||||||
|
|
||||||
function __construct(string $homedir)
|
function __construct(string $homedir)
|
||||||
{
|
{
|
||||||
$this->options['homedir'] = \rtrim($homedir, '/\\');
|
parent::__construct($homedir);
|
||||||
|
|
||||||
// the random seed file makes subsequent actions faster so only disable it if we have to.
|
// the random seed file makes subsequent actions faster so only disable it if we have to.
|
||||||
if ($this->options['homedir'] && !\is_writable($this->options['homedir'])) {
|
if ($this->options['homedir'] && !\is_writable($this->options['homedir'])) {
|
||||||
|
|
@ -114,85 +69,9 @@ class GPG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function __destruct()
|
|
||||||
{
|
|
||||||
$this->proc_close();
|
|
||||||
|
|
||||||
$gpgconf = static::findBinary('gpgconf');
|
|
||||||
if ($gpgconf) {
|
|
||||||
$env = ['GNUPGHOME' => $this->options['homedir']];
|
|
||||||
$pipes = [];
|
|
||||||
if ($process = \proc_open($gpgconf . ' --kill gpg-agent --homedir ' . \escapeshellarg($this->options['homedir']), [], $pipes, null, $env)) {
|
|
||||||
\proc_close($process);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isSupported() : bool
|
public static function isSupported() : bool
|
||||||
{
|
{
|
||||||
return \is_callable('proc_open') && static::findBinary('gpg');
|
return parent::isSupported() && static::findBinary('gpg');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a key for decryption
|
|
||||||
*/
|
|
||||||
public function addDecryptKey(string $fingerprint,
|
|
||||||
#[\SensitiveParameter]
|
|
||||||
string $passphrase
|
|
||||||
) : bool
|
|
||||||
{
|
|
||||||
$this->decryptKeys[$fingerprint] = $passphrase;
|
|
||||||
// $this->decryptKeys[\substr($fingerprint, -16)] = $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,
|
|
||||||
#[\SensitiveParameter]
|
|
||||||
string $passphrase
|
|
||||||
) : bool
|
|
||||||
{
|
|
||||||
$this->signKeys[$fingerprint] = $passphrase;
|
|
||||||
// $this->signKeys[\substr($fingerprint, -16)] = $passphrase;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all keys which were set for decryption before
|
|
||||||
*/
|
|
||||||
public function clearDecryptKeys() : bool
|
|
||||||
{
|
|
||||||
$this->decryptKeys = [];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
{
|
|
||||||
$this->signKeys = [];
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -388,19 +267,6 @@ class GPG
|
||||||
return $this->_exportKey($fingerprint, true);
|
return $this->_exportKey($fingerprint, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the engine info
|
|
||||||
*/
|
|
||||||
public function getEngineInfo() : array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'protocol' => null,
|
|
||||||
'file_name' => $this->binary,
|
|
||||||
'home_dir' => $this->options['homedir'],
|
|
||||||
'version' => $this->version
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the errortext, if a function fails
|
* Returns the errortext, if a function fails
|
||||||
*/
|
*/
|
||||||
|
|
@ -414,7 +280,7 @@ class GPG
|
||||||
*/
|
*/
|
||||||
public function getErrorInfo() : array
|
public function getErrorInfo() : array
|
||||||
{
|
{
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -422,16 +288,7 @@ class GPG
|
||||||
*/
|
*/
|
||||||
public function getProtocol() : int
|
public function getProtocol() : int
|
||||||
{
|
{
|
||||||
return 0;
|
return 0; // GPGME_PROTOCOL_OpenPGP
|
||||||
}
|
|
||||||
|
|
||||||
public function addPassphrase($keyId,
|
|
||||||
#[\SensitiveParameter]
|
|
||||||
$passphrase
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->passphrases[$keyId] = $passphrase;
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -439,7 +296,7 @@ class GPG
|
||||||
* Also saves revocation certificate in {homedir}/openpgp-revocs.d/
|
* Also saves revocation certificate in {homedir}/openpgp-revocs.d/
|
||||||
* https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html
|
* https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html
|
||||||
*/
|
*/
|
||||||
public function generateKey(GPGKeySettings $settings) /*: string|false*/
|
public function generateKey(PGPKeySettings $settings) /*: string|false*/
|
||||||
{
|
{
|
||||||
$arguments = [
|
$arguments = [
|
||||||
'--batch',
|
'--batch',
|
||||||
|
|
@ -715,15 +572,6 @@ class GPG
|
||||||
return $keys;
|
return $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the armored output
|
|
||||||
*/
|
|
||||||
public function setArmor(int $armor = 1) : bool
|
|
||||||
{
|
|
||||||
$this->armor = !!$armor;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the mode for error_reporting
|
* Sets the mode for error_reporting
|
||||||
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
|
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
|
||||||
|
|
@ -923,49 +771,6 @@ class GPG
|
||||||
return $this->_verify($fp, $signature);
|
return $this->_verify($fp, $signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function _debug(string $msg) : void
|
|
||||||
{
|
|
||||||
\SnappyMail\Log::debug('GPG', $msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
|
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
|
||||||
{
|
{
|
||||||
$this->_debug('BEGIN DETECT MESSAGE KEY IDs');
|
$this->_debug('BEGIN DETECT MESSAGE KEY IDs');
|
||||||
|
|
@ -1068,7 +873,7 @@ class GPG
|
||||||
throw new \Exception('Unable to open process.');
|
throw new \Exception('Unable to open process.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->_openPipes = new GpgProcPipes($proc_pipes);
|
$this->_openPipes = new ProcPipes($proc_pipes);
|
||||||
|
|
||||||
$this->_debug('BEGIN PROCESSING');
|
$this->_debug('BEGIN PROCESSING');
|
||||||
|
|
||||||
|
|
@ -1373,158 +1178,4 @@ class GPG
|
||||||
'errors' => $errors
|
'errors' => $errors
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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 '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function findBinary($name) : ?string
|
|
||||||
{
|
|
||||||
$binary = \trim((string) `which $name`);
|
|
||||||
if ($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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GpgProcPipes
|
|
||||||
{
|
|
||||||
private $pipes;
|
|
||||||
|
|
||||||
function __construct(array $pipes)
|
|
||||||
{
|
|
||||||
// Set streams as non-blocking.
|
|
||||||
foreach ($pipes as $pipe) {
|
|
||||||
\stream_set_blocking($pipe, 0);
|
|
||||||
\stream_set_write_buffer($pipe, GPG::CHUNK_SIZE);
|
|
||||||
\stream_set_chunk_size($pipe, GPG::CHUNK_SIZE);
|
|
||||||
\stream_set_read_buffer($pipe, GPG::CHUNK_SIZE);
|
|
||||||
}
|
|
||||||
$this->pipes = $pipes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function __destruct()
|
|
||||||
{
|
|
||||||
$this->closeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function closeAll() : void
|
|
||||||
{
|
|
||||||
foreach (\array_keys($this->pipes) as $number) {
|
|
||||||
$this->close($number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(int $number)
|
|
||||||
{
|
|
||||||
if (\array_key_exists($number, $this->pipes) && \is_resource($this->pipes[$number])) {
|
|
||||||
return $this->pipes[$number];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(int $number) : void
|
|
||||||
{
|
|
||||||
if (\array_key_exists($number, $this->pipes)) {
|
|
||||||
\fflush($this->pipes[$number]);
|
|
||||||
\fclose($this->pipes[$number]);
|
|
||||||
unset($this->pipes[$number]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private $buffers = [];
|
|
||||||
public function readPipeLines(int $number) : iterable
|
|
||||||
{
|
|
||||||
$pipe = $this->get($number);
|
|
||||||
if ($pipe) {
|
|
||||||
$chunk = \fread($pipe, GPG::CHUNK_SIZE);
|
|
||||||
$length = \strlen($chunk);
|
|
||||||
$eolLength = \strlen(PHP_EOL);
|
|
||||||
if (!isset($this->buffers[$number])) {
|
|
||||||
$this->buffers[$number] = '';
|
|
||||||
}
|
|
||||||
$this->buffers[$number] .= $chunk;
|
|
||||||
while (false !== ($pos = \strpos($this->buffers[$number], PHP_EOL))) {
|
|
||||||
yield \substr($this->buffers[$number], 0, $pos);
|
|
||||||
$this->buffers[$number] = \substr($this->buffers[$number], $pos + $eolLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function writePipe(int $number, string $data, int $length = 0) : int
|
|
||||||
{
|
|
||||||
$pipe = $this->get($number);
|
|
||||||
if ($pipe) {
|
|
||||||
$chunk = \substr($data, 0, $length ?: \strlen($data));
|
|
||||||
$length = \strlen($chunk);
|
|
||||||
$length = \fwrite($pipe, $chunk, $length);
|
|
||||||
if (!$length) {
|
|
||||||
// If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
|
|
||||||
// the pipe was seleted for writing, we assume it was EPIPE.
|
|
||||||
// There's no way to get the actual error code in PHP. See
|
|
||||||
// PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
|
|
||||||
$this->close($number);
|
|
||||||
}
|
|
||||||
return $length ?: 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace SnappyMail\PGP;
|
namespace SnappyMail\GPG;
|
||||||
|
|
||||||
class GPGKeySettings
|
class PGPKeySettings
|
||||||
{
|
{
|
||||||
public
|
public
|
||||||
// Primary key
|
// Primary key
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GnuPG
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SnappyMail\GPG;
|
||||||
|
|
||||||
|
class ProcPipes
|
||||||
|
{
|
||||||
|
private $pipes;
|
||||||
|
|
||||||
|
function __construct(array $pipes)
|
||||||
|
{
|
||||||
|
// Set streams as non-blocking.
|
||||||
|
foreach ($pipes as $pipe) {
|
||||||
|
\stream_set_blocking($pipe, 0);
|
||||||
|
\stream_set_write_buffer($pipe, Base::CHUNK_SIZE);
|
||||||
|
\stream_set_chunk_size($pipe, Base::CHUNK_SIZE);
|
||||||
|
\stream_set_read_buffer($pipe, Base::CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
$this->pipes = $pipes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __destruct()
|
||||||
|
{
|
||||||
|
$this->closeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeAll() : void
|
||||||
|
{
|
||||||
|
foreach (\array_keys($this->pipes) as $number) {
|
||||||
|
$this->close($number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(int $number)
|
||||||
|
{
|
||||||
|
if (\array_key_exists($number, $this->pipes) && \is_resource($this->pipes[$number])) {
|
||||||
|
return $this->pipes[$number];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(int $number) : void
|
||||||
|
{
|
||||||
|
if (\array_key_exists($number, $this->pipes)) {
|
||||||
|
\fflush($this->pipes[$number]);
|
||||||
|
\fclose($this->pipes[$number]);
|
||||||
|
unset($this->pipes[$number]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private $buffers = [];
|
||||||
|
public function readPipeLines(int $number) : iterable
|
||||||
|
{
|
||||||
|
$pipe = $this->get($number);
|
||||||
|
if ($pipe) {
|
||||||
|
$chunk = \fread($pipe, Base::CHUNK_SIZE);
|
||||||
|
$length = \strlen($chunk);
|
||||||
|
$eolLength = \strlen(\PHP_EOL);
|
||||||
|
if (!isset($this->buffers[$number])) {
|
||||||
|
$this->buffers[$number] = '';
|
||||||
|
}
|
||||||
|
$this->buffers[$number] .= $chunk;
|
||||||
|
while (false !== ($pos = \strpos($this->buffers[$number], \PHP_EOL))) {
|
||||||
|
yield \substr($this->buffers[$number], 0, $pos);
|
||||||
|
$this->buffers[$number] = \substr($this->buffers[$number], $pos + $eolLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writePipe(int $number, string $data, int $length = 0) : int
|
||||||
|
{
|
||||||
|
$pipe = $this->get($number);
|
||||||
|
if ($pipe) {
|
||||||
|
$chunk = \substr($data, 0, $length ?: \strlen($data));
|
||||||
|
$length = \strlen($chunk);
|
||||||
|
$length = \fwrite($pipe, $chunk, $length);
|
||||||
|
if (!$length) {
|
||||||
|
// If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
|
||||||
|
// the pipe was seleted for writing, we assume it was EPIPE.
|
||||||
|
// There's no way to get the actual error code in PHP. See
|
||||||
|
// PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
|
||||||
|
$this->close($number);
|
||||||
|
}
|
||||||
|
return $length ?: 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
1089
snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php
Normal file
1089
snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,13 +6,15 @@ defined('GNUPG_SIG_MODE_NORMAL') || define('GNUPG_SIG_MODE_NORMAL', 0);
|
||||||
defined('GNUPG_SIG_MODE_DETACH') || define('GNUPG_SIG_MODE_DETACH', 1);
|
defined('GNUPG_SIG_MODE_DETACH') || define('GNUPG_SIG_MODE_DETACH', 1);
|
||||||
defined('GNUPG_SIG_MODE_CLEAR') || define('GNUPG_SIG_MODE_CLEAR', 2);
|
defined('GNUPG_SIG_MODE_CLEAR') || define('GNUPG_SIG_MODE_CLEAR', 2);
|
||||||
|
|
||||||
|
use SnappyMail\GPG\PGP as GPG;
|
||||||
|
|
||||||
class GnuPG
|
class GnuPG
|
||||||
{
|
{
|
||||||
private
|
private
|
||||||
$homedir,
|
$homedir,
|
||||||
// Instance of gnupg pecl extension https://www.php.net/gnupg
|
// Instance of gnupg pecl extension https://www.php.net/gnupg
|
||||||
$GnuPG,
|
$GnuPG,
|
||||||
// Instance of \SnappyMail\PGP\GPG
|
// Instance of \SnappyMail\GPG\PGP
|
||||||
$GPG;
|
$GPG;
|
||||||
|
|
||||||
function __construct(string $homedir)
|
function __construct(string $homedir)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue