Import initial du site depuis le serveur
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| Abstract driver for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
abstract class enigma_driver
|
||||
{
|
||||
const SUPPORT_RSA = 'RSA';
|
||||
const SUPPORT_ECC = 'ECC';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string User name (email address)
|
||||
*/
|
||||
abstract function __construct($user);
|
||||
|
||||
/**
|
||||
* Driver initialization.
|
||||
*
|
||||
* @return mixed NULL on success, enigma_error on failure
|
||||
*/
|
||||
abstract function init();
|
||||
|
||||
/**
|
||||
* Encryption (and optional signing).
|
||||
*
|
||||
* @param string Message body
|
||||
* @param array List of keys (enigma_key objects)
|
||||
* @param enigma_key Optional signing Key ID
|
||||
*
|
||||
* @return mixed Encrypted message or enigma_error on failure
|
||||
*/
|
||||
abstract function encrypt($text, $keys, $sign_key = null);
|
||||
|
||||
/**
|
||||
* Decryption (and sig verification if sig exists).
|
||||
*
|
||||
* @param string Encrypted message
|
||||
* @param array List of key-password
|
||||
* @param enigma_signature Signature information (if available)
|
||||
*
|
||||
* @return mixed Decrypted message or enigma_error on failure
|
||||
*/
|
||||
abstract function decrypt($text, $keys = [], &$signature = null);
|
||||
|
||||
/**
|
||||
* Signing.
|
||||
*
|
||||
* @param string Message body
|
||||
* @param enigma_key The signing key
|
||||
* @param int Signing mode (enigma_engine::SIGN_*)
|
||||
*
|
||||
* @return mixed True on success or enigma_error on failure
|
||||
*/
|
||||
abstract function sign($text, $key, $mode = null);
|
||||
|
||||
/**
|
||||
* Signature verification.
|
||||
*
|
||||
* @param string Message body
|
||||
* @param string Signature, if message is of type PGP/MIME and body doesn't contain it
|
||||
*
|
||||
* @return mixed Signature information (enigma_signature) or enigma_error
|
||||
*/
|
||||
abstract function verify($text, $signature);
|
||||
|
||||
/**
|
||||
* Key/Cert file import.
|
||||
*
|
||||
* @param string File name or file content
|
||||
* @param bool True if first argument is a filename
|
||||
* @param array Optional key => password map
|
||||
*
|
||||
* @return mixed Import status array or enigma_error
|
||||
*/
|
||||
abstract function import($content, $isfile = false, $passwords = []);
|
||||
|
||||
/**
|
||||
* Key/Cert export.
|
||||
*
|
||||
* @param string Key ID
|
||||
* @param bool Include private key
|
||||
* @param array Optional key => password map
|
||||
*
|
||||
* @return mixed Key content or enigma_error
|
||||
*/
|
||||
abstract function export($key, $with_private = false, $passwords = []);
|
||||
|
||||
/**
|
||||
* Keys listing.
|
||||
*
|
||||
* @param string Optional pattern for key ID, user ID or fingerprint
|
||||
*
|
||||
* @return mixed Array of enigma_key objects or enigma_error
|
||||
*/
|
||||
abstract function list_keys($pattern = '');
|
||||
|
||||
/**
|
||||
* Single key information.
|
||||
*
|
||||
* @param string Key ID, user ID or fingerprint
|
||||
*
|
||||
* @return mixed Key (enigma_key) object or enigma_error
|
||||
*/
|
||||
abstract function get_key($keyid);
|
||||
|
||||
/**
|
||||
* Key pair generation.
|
||||
*
|
||||
* @param array Key/User data (name, email, password, size)
|
||||
*
|
||||
* @return mixed Key (enigma_key) object or enigma_error
|
||||
*/
|
||||
abstract function gen_key($data);
|
||||
|
||||
/**
|
||||
* Key deletion.
|
||||
*
|
||||
* @param string Key ID
|
||||
*
|
||||
* @return mixed True on success or enigma_error
|
||||
*/
|
||||
abstract function delete_key($keyid);
|
||||
|
||||
/**
|
||||
* Returns a name of the hash algorithm used for the last
|
||||
* signing operation.
|
||||
*
|
||||
* @return string Hash algorithm name e.g. sha1
|
||||
*/
|
||||
abstract function signature_algorithm();
|
||||
|
||||
/**
|
||||
* Returns a list of supported features.
|
||||
*
|
||||
* @return array Capabilities list
|
||||
*/
|
||||
public function capabilities()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,791 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| GnuPG (PGP) driver for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
require_once 'Crypt/GPG.php';
|
||||
|
||||
class enigma_driver_gnupg extends enigma_driver
|
||||
{
|
||||
protected $rc;
|
||||
protected $gpg;
|
||||
protected $homedir;
|
||||
protected $user;
|
||||
protected $last_sig_algorithm;
|
||||
protected $debug = false;
|
||||
protected $db_files = ['pubring.gpg', 'secring.gpg', 'pubring.kbx'];
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param rcube_user $user User object
|
||||
*/
|
||||
function __construct($user)
|
||||
{
|
||||
$this->rc = rcmail::get_instance();
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver initialization and environment checking.
|
||||
* Should only return critical errors.
|
||||
*
|
||||
* @return enigma_error|null NULL on success, enigma_error on failure
|
||||
*/
|
||||
function init()
|
||||
{
|
||||
$homedir = $this->rc->config->get('enigma_pgp_homedir');
|
||||
$debug = $this->rc->config->get('enigma_debug');
|
||||
$binary = $this->rc->config->get('enigma_pgp_binary');
|
||||
$agent = $this->rc->config->get('enigma_pgp_agent');
|
||||
$gpgconf = $this->rc->config->get('enigma_pgp_gpgconf');
|
||||
|
||||
if (!$homedir) {
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Option 'enigma_pgp_homedir' not specified");
|
||||
}
|
||||
|
||||
// check if homedir exists (create it if not) and is readable
|
||||
if (!file_exists($homedir)) {
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Keys directory doesn't exists: $homedir");
|
||||
}
|
||||
if (!is_writable($homedir)) {
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Keys directory isn't writeable: $homedir");
|
||||
}
|
||||
|
||||
$homedir = $homedir . '/' . $this->user;
|
||||
|
||||
// check if user's homedir exists (create it if not) and is readable
|
||||
if (!file_exists($homedir)) {
|
||||
mkdir($homedir, 0700);
|
||||
}
|
||||
|
||||
if (!file_exists($homedir)) {
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Unable to create keys directory: $homedir");
|
||||
}
|
||||
if (!is_writable($homedir)) {
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Unable to write to keys directory: $homedir");
|
||||
}
|
||||
|
||||
$this->debug = $debug;
|
||||
$this->homedir = $homedir;
|
||||
|
||||
$options = ['homedir' => $this->homedir];
|
||||
|
||||
if ($debug) {
|
||||
$options['debug'] = [$this, 'debug'];
|
||||
}
|
||||
if ($binary) {
|
||||
$options['binary'] = $binary;
|
||||
}
|
||||
if ($agent) {
|
||||
$options['agent'] = $agent;
|
||||
}
|
||||
if ($gpgconf) {
|
||||
$options['gpgconf'] = $gpgconf;
|
||||
}
|
||||
|
||||
$options['cipher-algo'] = $this->rc->config->get('enigma_pgp_cipher_algo');
|
||||
$options['digest-algo'] = $this->rc->config->get('enigma_pgp_digest_algo');
|
||||
|
||||
// Create Crypt_GPG object
|
||||
try {
|
||||
$this->gpg = new Crypt_GPG($options);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
|
||||
$this->db_sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption (and optional signing).
|
||||
*
|
||||
* @param string $text Message body
|
||||
* @param array $keys List of keys (enigma_key objects)
|
||||
* @param enigma_key $sign_key Optional signing Key ID
|
||||
*
|
||||
* @return string|enigma_error Encrypted message or enigma_error on failure
|
||||
*/
|
||||
function encrypt($text, $keys, $sign_key = null)
|
||||
{
|
||||
try {
|
||||
foreach ($keys as $key) {
|
||||
$this->gpg->addEncryptKey($key->reference);
|
||||
}
|
||||
|
||||
if ($sign_key) {
|
||||
$this->gpg->addSignKey($sign_key->reference, $sign_key->password);
|
||||
|
||||
$res = $this->gpg->encryptAndSign($text, true);
|
||||
$sigInfo = $this->gpg->getLastSignatureInfo();
|
||||
|
||||
$this->last_sig_algorithm = $sigInfo->getHashAlgorithmName();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $this->gpg->encrypt($text, true);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a message (and verify if signature found)
|
||||
*
|
||||
* @param string $text Encrypted message
|
||||
* @param array $keys List of key-password mapping
|
||||
* @param enigma_signature &$signature Signature information (if available)
|
||||
*
|
||||
* @return mixed Decrypted message or enigma_error on failure
|
||||
*/
|
||||
function decrypt($text, $keys = [], &$signature = null)
|
||||
{
|
||||
try {
|
||||
foreach ($keys as $key => $password) {
|
||||
$this->gpg->addDecryptKey($key, $password);
|
||||
}
|
||||
|
||||
$result = $this->gpg->decryptAndVerify($text, true);
|
||||
|
||||
if (!empty($result['signatures'])) {
|
||||
$signature = $this->parse_signature($result['signatures'][0]);
|
||||
}
|
||||
|
||||
// EFAIL vulnerability mitigation (#6289)
|
||||
// Handle MDC warning as an exception, this is the default for gpg 2.3.
|
||||
if (method_exists($this->gpg, 'getWarnings')) {
|
||||
foreach ($this->gpg->getWarnings() as $warning_msg) {
|
||||
if (strpos($warning_msg, 'not integrity protected') !== false) {
|
||||
return new enigma_error(enigma_error::NOMDC, ucfirst($warning_msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signing.
|
||||
*
|
||||
* @param string $text Message body
|
||||
* @param enigma_key $key The key
|
||||
* @param int $mode Signing mode (enigma_engine::SIGN_*)
|
||||
*
|
||||
* @return mixed True on success or enigma_error on failure
|
||||
*/
|
||||
function sign($text, $key, $mode = null)
|
||||
{
|
||||
try {
|
||||
$this->gpg->addSignKey($key->reference, $key->password);
|
||||
|
||||
$res = $this->gpg->sign($text, $mode, Crypt_GPG::ARMOR_ASCII, true);
|
||||
$sigInfo = $this->gpg->getLastSignatureInfo();
|
||||
|
||||
$this->last_sig_algorithm = $sigInfo->getHashAlgorithmName();
|
||||
|
||||
return $res;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signature verification.
|
||||
*
|
||||
* @param string $text Message body
|
||||
* @param string $signature Signature, if message is of type PGP/MIME and body doesn't contain it
|
||||
*
|
||||
* @return enigma_signature|enigma_error Signature information or enigma_error
|
||||
*/
|
||||
function verify($text, $signature)
|
||||
{
|
||||
try {
|
||||
$verified = $this->gpg->verify($text, $signature);
|
||||
return $this->parse_signature($verified[0]);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key file import.
|
||||
*
|
||||
* @param string $content File name or file content
|
||||
* @param bool $isfile True if first argument is a filename
|
||||
* @param array $password Optional key => password map
|
||||
*
|
||||
* @return mixed Import status array or enigma_error
|
||||
*/
|
||||
public function import($content, $isfile = false, $passwords = [])
|
||||
{
|
||||
try {
|
||||
// GnuPG 2.1 requires secret key passphrases on import
|
||||
foreach ($passwords as $keyid => $pass) {
|
||||
$this->gpg->addPassphrase($keyid, $pass);
|
||||
}
|
||||
|
||||
if ($isfile) {
|
||||
$result = $this->gpg->importKeyFile($content);
|
||||
}
|
||||
else {
|
||||
$result = $this->gpg->importKey($content);
|
||||
}
|
||||
|
||||
$this->db_save();
|
||||
|
||||
return $result;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key export.
|
||||
*
|
||||
* @param string $keyid Key ID
|
||||
* @param bool $with_private Include private key
|
||||
* @param array $passwords Optional key => password map
|
||||
*
|
||||
* @return string|enigma_error Key content or enigma_error
|
||||
*/
|
||||
public function export($keyid, $with_private = false, $passwords = [])
|
||||
{
|
||||
try {
|
||||
$key = $this->gpg->exportPublicKey($keyid, true);
|
||||
|
||||
if ($with_private) {
|
||||
// GnuPG 2.1 requires secret key passphrases on export
|
||||
foreach ($passwords as $_keyid => $pass) {
|
||||
$this->gpg->addPassphrase($_keyid, $pass);
|
||||
}
|
||||
|
||||
$priv = $this->gpg->exportPrivateKey($keyid, true);
|
||||
$key .= $priv;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys listing.
|
||||
*
|
||||
* @param string $patter Optional pattern for key ID, user ID or fingerprint
|
||||
*
|
||||
* @return enigma_key[]|enigma_error Array of keys or enigma_error
|
||||
*/
|
||||
public function list_keys($pattern = '')
|
||||
{
|
||||
try {
|
||||
$keys = $this->gpg->getKeys($pattern);
|
||||
$result = [];
|
||||
|
||||
foreach ($keys as $idx => $key) {
|
||||
$result[] = $this->parse_key($key);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single key information.
|
||||
*
|
||||
* @param string $keyid Key ID, user ID or fingerprint
|
||||
*
|
||||
* @return enigma_key|enigma_error Key object or enigma_error
|
||||
*/
|
||||
public function get_key($keyid)
|
||||
{
|
||||
$list = $this->list_keys($keyid);
|
||||
|
||||
if (is_array($list)) {
|
||||
return $list[key($list)];
|
||||
}
|
||||
|
||||
// error
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key pair generation.
|
||||
*
|
||||
* @param array $data Key/User data (user, email, password, size)
|
||||
*
|
||||
* @return mixed Key (enigma_key) object or enigma_error
|
||||
*/
|
||||
public function gen_key($data)
|
||||
{
|
||||
try {
|
||||
$debug = $this->rc->config->get('enigma_debug');
|
||||
$keygen = new Crypt_GPG_KeyGenerator([
|
||||
'homedir' => $this->homedir,
|
||||
// 'binary' => '/usr/bin/gpg2',
|
||||
'debug' => $debug ? [$this, 'debug'] : false,
|
||||
]);
|
||||
|
||||
$key = $keygen
|
||||
->setExpirationDate(0)
|
||||
->setPassphrase($data['password'])
|
||||
->generateKey($data['user'], $data['email']);
|
||||
|
||||
return $this->parse_key($key);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key deletion.
|
||||
*
|
||||
* @param string $keyid Key ID
|
||||
*
|
||||
* @return mixed True on success or enigma_error
|
||||
*/
|
||||
public function delete_key($keyid)
|
||||
{
|
||||
// delete public key
|
||||
$result = $this->delete_pubkey($keyid);
|
||||
|
||||
// error handling
|
||||
if ($result !== true) {
|
||||
$code = $result->getCode();
|
||||
|
||||
// if not found, delete private key
|
||||
if ($code == enigma_error::KEYNOTFOUND) {
|
||||
$result = $this->delete_privkey($keyid);
|
||||
}
|
||||
// need to delete private key first
|
||||
else if ($code == enigma_error::DELKEY) {
|
||||
$result = $this->delete_privkey($keyid);
|
||||
|
||||
if ($result === true) {
|
||||
$result = $this->delete_pubkey($keyid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->db_save();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a name of the hash algorithm used for the last
|
||||
* signing operation.
|
||||
*
|
||||
* @return string Hash algorithm name e.g. sha1
|
||||
*/
|
||||
public function signature_algorithm()
|
||||
{
|
||||
return $this->last_sig_algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of supported features.
|
||||
*
|
||||
* @return array Capabilities list
|
||||
*/
|
||||
public function capabilities()
|
||||
{
|
||||
$caps = [enigma_driver::SUPPORT_RSA];
|
||||
$version = $this->gpg->getVersion();
|
||||
|
||||
if (version_compare($version, '2.1.7', 'ge')) {
|
||||
$caps[] = enigma_driver::SUPPORT_ECC;
|
||||
}
|
||||
|
||||
return $caps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private key deletion.
|
||||
*/
|
||||
protected function delete_privkey($keyid)
|
||||
{
|
||||
try {
|
||||
$this->gpg->deletePrivateKey($keyid);
|
||||
return true;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public key deletion.
|
||||
*/
|
||||
protected function delete_pubkey($keyid)
|
||||
{
|
||||
try {
|
||||
$this->gpg->deletePublicKey($keyid);
|
||||
return true;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return $this->get_error_from_exception($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Crypt_GPG exception into Enigma's error object
|
||||
*
|
||||
* @param mixed $e Exception object
|
||||
*
|
||||
* @return enigma_error Error object
|
||||
*/
|
||||
protected function get_error_from_exception($e)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
if ($e instanceof Crypt_GPG_KeyNotFoundException) {
|
||||
$error = enigma_error::KEYNOTFOUND;
|
||||
$data['id'] = $e->getKeyId();
|
||||
}
|
||||
else if ($e instanceof Crypt_GPG_BadPassphraseException) {
|
||||
$error = enigma_error::BADPASS;
|
||||
$data['bad'] = $e->getBadPassphrases();
|
||||
$data['missing'] = $e->getMissingPassphrases();
|
||||
}
|
||||
else if ($e instanceof Crypt_GPG_NoDataException) {
|
||||
$error = enigma_error::NODATA;
|
||||
}
|
||||
else if ($e instanceof Crypt_GPG_DeletePrivateKeyException) {
|
||||
$error = enigma_error::DELKEY;
|
||||
}
|
||||
else {
|
||||
$error = enigma_error::INTERNAL;
|
||||
}
|
||||
|
||||
$msg = $e->getMessage();
|
||||
|
||||
return new enigma_error($error, $msg, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Crypt_GPG_Signature object into Enigma's signature object
|
||||
*
|
||||
* @param Crypt_GPG_Signature $sig Signature object
|
||||
*
|
||||
* @return enigma_signature Signature object
|
||||
*/
|
||||
protected function parse_signature($sig)
|
||||
{
|
||||
$data = new enigma_signature();
|
||||
|
||||
$data->id = $sig->getId() ?: $sig->getKeyId();
|
||||
$data->valid = $sig->isValid();
|
||||
$data->fingerprint = $sig->getKeyFingerprint();
|
||||
$data->created = $sig->getCreationDate();
|
||||
$data->expires = $sig->getExpirationDate();
|
||||
|
||||
// In case of ERRSIG user may not be set
|
||||
if ($user = $sig->getUserId()) {
|
||||
$data->name = $user->getName();
|
||||
$data->comment = $user->getComment();
|
||||
$data->email = $user->getEmail();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Crypt_GPG_Key object into Enigma's key object
|
||||
*
|
||||
* @param Crypt_GPG_Key $key Key object
|
||||
*
|
||||
* @return enigma_key Key object
|
||||
*/
|
||||
protected function parse_key($key)
|
||||
{
|
||||
$ekey = new enigma_key();
|
||||
|
||||
foreach ($key->getUserIds() as $idx => $user) {
|
||||
$id = new enigma_userid();
|
||||
$id->name = $user->getName();
|
||||
$id->comment = $user->getComment();
|
||||
$id->email = $user->getEmail();
|
||||
$id->valid = $user->isValid();
|
||||
$id->revoked = $user->isRevoked();
|
||||
|
||||
$ekey->users[$idx] = $id;
|
||||
}
|
||||
|
||||
$ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
|
||||
|
||||
// keep reference to Crypt_GPG's key for performance reasons
|
||||
$ekey->reference = $key;
|
||||
|
||||
foreach ($key->getSubKeys() as $idx => $subkey) {
|
||||
$skey = new enigma_subkey();
|
||||
$skey->id = $subkey->getId();
|
||||
$skey->revoked = $subkey->isRevoked();
|
||||
$skey->fingerprint = $subkey->getFingerprint();
|
||||
$skey->has_private = $subkey->hasPrivate();
|
||||
$skey->algorithm = $subkey->getAlgorithm();
|
||||
$skey->length = $subkey->getLength();
|
||||
$skey->usage = $subkey->usage();
|
||||
|
||||
if (method_exists($subkey, 'getCreationDateTime')) {
|
||||
$skey->created = $subkey->getCreationDateTime();
|
||||
$skey->expires = $subkey->getExpirationDateTime();
|
||||
}
|
||||
else {
|
||||
$skey->created = $subkey->getCreationDate();
|
||||
$skey->expires = $subkey->getExpirationDate();
|
||||
|
||||
if ($skey->created) {
|
||||
$skey->created = new DateTime("@{$skey->created}");
|
||||
}
|
||||
|
||||
if ($skey->expires) {
|
||||
$skey->expires = new DateTime("@{$skey->expires}");
|
||||
}
|
||||
}
|
||||
|
||||
$ekey->subkeys[$idx] = $skey;
|
||||
};
|
||||
|
||||
$ekey->id = $ekey->subkeys[0]->id;
|
||||
|
||||
return $ekey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize keys database on multi-host setups
|
||||
*/
|
||||
protected function db_sync()
|
||||
{
|
||||
if (!$this->rc->config->get('enigma_multihost')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db = $this->rc->get_dbh();
|
||||
$table = $db->table_name('filestore', true);
|
||||
$files = [];
|
||||
|
||||
$result = $db->query(
|
||||
"SELECT `file_id`, `filename`, `mtime` FROM $table WHERE `user_id` = ? AND `context` = ?",
|
||||
$this->rc->user->ID, 'enigma'
|
||||
);
|
||||
|
||||
while ($record = $db->fetch_assoc($result)) {
|
||||
$file = $this->homedir . '/' . $record['filename'];
|
||||
$mtime = @filemtime($file);
|
||||
$files[] = $record['filename'];
|
||||
|
||||
if ($mtime < $record['mtime']) {
|
||||
$data_result = $db->query("SELECT `data`, `mtime` FROM $table"
|
||||
. " WHERE `file_id` = ?", $record['file_id']
|
||||
);
|
||||
|
||||
$record = $db->fetch_assoc($data_result);
|
||||
$data = $record ? base64_decode($record['data']) : null;
|
||||
|
||||
if ($data === null || $data === false) {
|
||||
rcube::raise_error([
|
||||
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Enigma: Failed to sync $file ({$record['file_id']}). Decode error."
|
||||
], true, false);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Private keys might be located in 'private-keys-v1.d' subdirectory. Make sure it exists.
|
||||
if (strpos($file, '/private-keys-v1.d/')) {
|
||||
if (!file_exists($this->homedir . '/private-keys-v1.d')) {
|
||||
mkdir($this->homedir . '/private-keys-v1.d', 0700);
|
||||
}
|
||||
}
|
||||
|
||||
$tmpfile = $file . '.tmp';
|
||||
|
||||
if (file_put_contents($tmpfile, $data, LOCK_EX) === strlen($data)) {
|
||||
rename($tmpfile, $file);
|
||||
touch($file, $record['mtime']);
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug("SYNC: Fetched file: $file");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// error
|
||||
@unlink($tmpfile);
|
||||
|
||||
rcube::raise_error([
|
||||
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Enigma: Failed to sync $file."
|
||||
], true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove files not in database
|
||||
if (!$db->is_error($result)) {
|
||||
foreach (array_diff($this->db_files_list(), $files) as $file) {
|
||||
$file = $this->homedir . '/' . $file;
|
||||
|
||||
if (unlink($file)) {
|
||||
if ($this->debug) {
|
||||
$this->debug("SYNC: Removed file: $file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No records found, do initial sync if already have the keyring
|
||||
if (!$db->is_error($result) && empty($file)) {
|
||||
$this->db_save(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save keys database for multi-host setups
|
||||
*/
|
||||
protected function db_save($is_empty = false)
|
||||
{
|
||||
if (!$this->rc->config->get('enigma_multihost')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$db = $this->rc->get_dbh();
|
||||
$table = $db->table_name('filestore', true);
|
||||
$records = [];
|
||||
|
||||
if (!$is_empty) {
|
||||
$result = $db->query(
|
||||
"SELECT `file_id`, `filename`, `mtime` FROM $table WHERE `user_id` = ? AND `context` = ?",
|
||||
$this->rc->user->ID, 'enigma'
|
||||
);
|
||||
|
||||
while ($record = $db->fetch_assoc($result)) {
|
||||
$records[$record['filename']] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->db_files_list() as $filename) {
|
||||
$file = $this->homedir . '/' . $filename;
|
||||
$mtime = @filemtime($file);
|
||||
|
||||
$existing = !empty($records[$filename]) ? $records[$filename] : null;
|
||||
unset($records[$filename]);
|
||||
|
||||
if ($mtime && (empty($existing) || $mtime > $existing['mtime'])) {
|
||||
$data = file_get_contents($file);
|
||||
$data = base64_encode($data);
|
||||
$datasize = strlen($data);
|
||||
|
||||
if (empty($maxsize)) {
|
||||
$maxsize = min($db->get_variable('max_allowed_packet', 1048500), 4*1024*1024) - 2000;
|
||||
}
|
||||
|
||||
if ($datasize > $maxsize) {
|
||||
rcube::raise_error([
|
||||
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Enigma: Failed to save $file. Size exceeds max_allowed_packet."
|
||||
], true, false);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$unique = ['user_id' => $this->rc->user->ID, 'context' => 'enigma', 'filename' => $filename];
|
||||
$result = $db->insert_or_update($table, $unique, ['mtime', 'data'], [$mtime, $data]);
|
||||
|
||||
if ($db->is_error($result)) {
|
||||
rcube::raise_error([
|
||||
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Enigma: Failed to save $file into database."
|
||||
], true, false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug("SYNC: Pushed file: $file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete removed files from database
|
||||
foreach (array_keys($records) as $filename) {
|
||||
$file = $this->homedir . '/' . $filename;
|
||||
$result = $db->query("DELETE FROM $table WHERE `user_id` = ? AND `context` = ? AND `filename` = ?",
|
||||
$this->rc->user->ID, 'enigma', $filename
|
||||
);
|
||||
|
||||
if ($db->is_error($result)) {
|
||||
rcube::raise_error([
|
||||
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
|
||||
'message' => "Enigma: Failed to delete $file from database."
|
||||
], true, false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$this->debug("SYNC: Removed file: $file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of homedir files to backup
|
||||
*/
|
||||
protected function db_files_list()
|
||||
{
|
||||
$files = [];
|
||||
|
||||
foreach ($this->db_files as $file) {
|
||||
if (file_exists($this->homedir . '/' . $file)) {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (glob($this->homedir . '/private-keys-v1.d/*.key') as $file) {
|
||||
$files[] = ltrim(substr($file, strlen($this->homedir)), '/');
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write debug info from Crypt_GPG to logs/enigma
|
||||
*/
|
||||
public function debug($line)
|
||||
{
|
||||
rcube::write_log('enigma', 'GPG: ' . $line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| S/MIME driver for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_driver_phpssl extends enigma_driver
|
||||
{
|
||||
private $rc;
|
||||
private $homedir;
|
||||
private $user;
|
||||
|
||||
function __construct($user)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$this->rc = $rcmail;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver initialization and environment checking.
|
||||
* Should only return critical errors.
|
||||
*
|
||||
* @return mixed NULL on success, enigma_error on failure
|
||||
*/
|
||||
function init()
|
||||
{
|
||||
$homedir = $this->rc->config->get('enigma_smime_homedir', INSTALL_PATH . '/plugins/enigma/home');
|
||||
|
||||
if (!$homedir)
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Option 'enigma_smime_homedir' not specified");
|
||||
|
||||
// check if homedir exists (create it if not) and is readable
|
||||
if (!file_exists($homedir))
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Keys directory doesn't exists: $homedir");
|
||||
if (!is_writable($homedir))
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Keys directory isn't writeable: $homedir");
|
||||
|
||||
$homedir = $homedir . '/' . $this->user;
|
||||
|
||||
// check if user's homedir exists (create it if not) and is readable
|
||||
if (!file_exists($homedir))
|
||||
mkdir($homedir, 0700);
|
||||
|
||||
if (!file_exists($homedir))
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Unable to create keys directory: $homedir");
|
||||
if (!is_writable($homedir))
|
||||
return new enigma_error(enigma_error::INTERNAL,
|
||||
"Unable to write to keys directory: $homedir");
|
||||
|
||||
$this->homedir = $homedir;
|
||||
|
||||
}
|
||||
|
||||
function encrypt($text, $keys, $sign_key = null)
|
||||
{
|
||||
}
|
||||
|
||||
function decrypt($text, $keys = [], &$signature = null)
|
||||
{
|
||||
}
|
||||
|
||||
function sign($text, $key, $mode = null)
|
||||
{
|
||||
}
|
||||
|
||||
function verify($struct, $message)
|
||||
{
|
||||
// use common temp dir
|
||||
$msg_file = rcube_utils::temp_filename('enigmsg');
|
||||
$cert_file = rcube_utils::temp_filename('enigcrt');
|
||||
|
||||
$fh = fopen($msg_file, "w");
|
||||
if ($struct->mime_id) {
|
||||
$message->get_part_body($struct->mime_id, false, 0, $fh);
|
||||
}
|
||||
else {
|
||||
$this->rc->storage->get_raw_body($message->uid, $fh);
|
||||
}
|
||||
fclose($fh);
|
||||
|
||||
// @TODO: use stored certificates
|
||||
|
||||
// try with certificate verification
|
||||
$sig = openssl_pkcs7_verify($msg_file, 0, $cert_file);
|
||||
$validity = true;
|
||||
|
||||
if ($sig !== true) {
|
||||
// try without certificate verification
|
||||
$sig = openssl_pkcs7_verify($msg_file, PKCS7_NOVERIFY, $cert_file);
|
||||
$validity = enigma_error::UNVERIFIED;
|
||||
}
|
||||
|
||||
if ($sig === true) {
|
||||
$sig = $this->parse_sig_cert($cert_file, $validity);
|
||||
}
|
||||
else {
|
||||
$errorstr = $this->get_openssl_error();
|
||||
$sig = new enigma_error(enigma_error::INTERNAL, $errorstr);
|
||||
}
|
||||
|
||||
// remove temp files
|
||||
@unlink($msg_file);
|
||||
@unlink($cert_file);
|
||||
|
||||
return $sig;
|
||||
}
|
||||
|
||||
public function import($content, $isfile = false, $passwords = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function export($key, $with_private = false, $passwords = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function list_keys($pattern='')
|
||||
{
|
||||
}
|
||||
|
||||
public function get_key($keyid)
|
||||
{
|
||||
}
|
||||
|
||||
public function gen_key($data)
|
||||
{
|
||||
}
|
||||
|
||||
public function delete_key($keyid)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a name of the hash algorithm used for the last
|
||||
* signing operation.
|
||||
*
|
||||
* @return string Hash algorithm name e.g. sha1
|
||||
*/
|
||||
public function signature_algorithm()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Crypt_GPG_Key object into Enigma's key object
|
||||
*
|
||||
* @param Crypt_GPG_Key Key object
|
||||
*
|
||||
* @return enigma_key Key object
|
||||
*/
|
||||
private function parse_key($key)
|
||||
{
|
||||
}
|
||||
|
||||
private function get_openssl_error()
|
||||
{
|
||||
$tmp = [];
|
||||
while ($errorstr = openssl_error_string()) {
|
||||
$tmp[] = $errorstr;
|
||||
}
|
||||
|
||||
return join("\n", array_values($tmp));
|
||||
}
|
||||
|
||||
private function parse_sig_cert($file, $validity)
|
||||
{
|
||||
$cert = openssl_x509_parse(file_get_contents($file));
|
||||
|
||||
if (empty($cert) || empty($cert['subject'])) {
|
||||
$errorstr = $this->get_openssl_error();
|
||||
return new enigma_error(enigma_error::INTERNAL, $errorstr);
|
||||
}
|
||||
|
||||
$data = new enigma_signature();
|
||||
|
||||
$data->id = $cert['hash']; //?
|
||||
$data->valid = $validity;
|
||||
$data->fingerprint = $cert['serialNumber'];
|
||||
$data->created = $cert['validFrom_time_t'];
|
||||
$data->expires = $cert['validTo_time_t'];
|
||||
$data->name = $cert['subject']['CN'];
|
||||
// $data->comment = '';
|
||||
$data->email = $cert['subject']['emailAddress'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| Error class for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_error
|
||||
{
|
||||
private $code;
|
||||
private $message;
|
||||
private $data = [];
|
||||
|
||||
// error codes
|
||||
const OK = 0;
|
||||
const INTERNAL = 1;
|
||||
const NODATA = 2;
|
||||
const KEYNOTFOUND = 3;
|
||||
const DELKEY = 4;
|
||||
const BADPASS = 5;
|
||||
const EXPIRED = 6;
|
||||
const UNVERIFIED = 7;
|
||||
const NOMDC = 8;
|
||||
|
||||
|
||||
function __construct($code = null, $message = '', $data = [])
|
||||
{
|
||||
$this->code = $code;
|
||||
$this->message = $message;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
function getCode()
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
function getData($name = null)
|
||||
{
|
||||
if ($name) {
|
||||
return $this->data[$name] ?? null;
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| Key class for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_key
|
||||
{
|
||||
public $id;
|
||||
public $name;
|
||||
public $users = [];
|
||||
public $subkeys = [];
|
||||
public $reference;
|
||||
public $password;
|
||||
|
||||
const TYPE_UNKNOWN = 0;
|
||||
const TYPE_KEYPAIR = 1;
|
||||
const TYPE_PUBLIC = 2;
|
||||
|
||||
const CAN_ENCRYPT = 1;
|
||||
const CAN_SIGN = 2;
|
||||
const CAN_CERTIFY = 4;
|
||||
const CAN_AUTHENTICATE = 8;
|
||||
|
||||
|
||||
/**
|
||||
* Keys list sorting callback for usort()
|
||||
*/
|
||||
static function cmp($a, $b)
|
||||
{
|
||||
return strcmp($a->name, $b->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key type
|
||||
*
|
||||
* @return int One of self::TYPE_* constant values
|
||||
*/
|
||||
function get_type()
|
||||
{
|
||||
if (!empty($this->subkeys[0]) && $this->subkeys[0]->has_private) {
|
||||
return enigma_key::TYPE_KEYPAIR;
|
||||
}
|
||||
else if (!empty($this->subkeys[0])) {
|
||||
return enigma_key::TYPE_PUBLIC;
|
||||
}
|
||||
|
||||
return enigma_key::TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all subkeys are revoked
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_revoked()
|
||||
{
|
||||
foreach ($this->subkeys as $subkey) {
|
||||
if (!$subkey->revoked) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($this->subkeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any user ID is valid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_valid()
|
||||
{
|
||||
foreach ($this->users as $user) {
|
||||
if ($user->valid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of subkeys is a private key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_private()
|
||||
{
|
||||
foreach ($this->subkeys as $subkey) {
|
||||
if ($subkey->has_private) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key ID by user email
|
||||
*
|
||||
* @param string $email Email address
|
||||
* @param int $mode Key mode (see self::CAN_* constants)
|
||||
*
|
||||
* @return enigma_subkey|null Subkey object
|
||||
*/
|
||||
function find_subkey($email, $mode)
|
||||
{
|
||||
foreach ($this->users as $user) {
|
||||
if (strcasecmp($user->email, $email) === 0 && $user->valid && !$user->revoked) {
|
||||
foreach ($this->subkeys as $subkey) {
|
||||
if (!$subkey->revoked && !$subkey->is_expired()) {
|
||||
if ($subkey->usage & $mode) {
|
||||
return $subkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts long ID or Fingerprint to short ID
|
||||
* Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
|
||||
*
|
||||
* @param string $id Key ID or fingerprint
|
||||
*
|
||||
* @return string Key short ID
|
||||
*/
|
||||
static function format_id($id)
|
||||
{
|
||||
// E.g. 04622F2089E037A5 => 89E037A5
|
||||
|
||||
return substr($id, -8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats fingerprint string
|
||||
*
|
||||
* @param string $fingerprint Key fingerprint
|
||||
*
|
||||
* @return string Formatted fingerprint (with spaces)
|
||||
*/
|
||||
static function format_fingerprint($fingerprint)
|
||||
{
|
||||
if (!$fingerprint) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = '';
|
||||
for ($i=0; $i<40; $i++) {
|
||||
if ($i % 4 == 0) {
|
||||
$result .= ' ';
|
||||
}
|
||||
$result .= $fingerprint[$i];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| Mail_mime wrapper for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_mime_message extends Mail_mime
|
||||
{
|
||||
const PGP_SIGNED = 1;
|
||||
const PGP_ENCRYPTED = 2;
|
||||
|
||||
protected $type;
|
||||
protected $message;
|
||||
protected $body;
|
||||
protected $signature;
|
||||
protected $encrypted;
|
||||
protected $micalg;
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param Mail_mime Original message
|
||||
* @param int Output message type
|
||||
*/
|
||||
function __construct($message, $type)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->type = $type;
|
||||
|
||||
// clone parameters
|
||||
foreach (array_keys($this->build_params) as $param) {
|
||||
$this->build_params[$param] = $message->getParam($param);
|
||||
}
|
||||
|
||||
// clone headers
|
||||
$this->headers = $message->headers();
|
||||
|
||||
// \r\n is must-have here
|
||||
$this->body = $message->get() . "\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the message is multipart (requires PGP/MIME)
|
||||
*
|
||||
* @return bool True if it is multipart, otherwise False
|
||||
*/
|
||||
public function isMultipart()
|
||||
{
|
||||
return $this->message instanceof enigma_mime_message
|
||||
|| $this->message->isMultipart() || $this->message->getHTMLBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get e-mail address of message sender
|
||||
*
|
||||
* @return string|null Sender address
|
||||
*/
|
||||
public function getFromAddress()
|
||||
{
|
||||
// get sender address
|
||||
$headers = $this->message->headers();
|
||||
|
||||
if (isset($headers['From'])) {
|
||||
$from = rcube_mime::decode_address_list($headers['From'], 1, false, null, true);
|
||||
$from = $from[1] ?? null;
|
||||
|
||||
return $from;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recipients' e-mail addresses
|
||||
*
|
||||
* @return array Recipients' addresses
|
||||
*/
|
||||
public function getRecipients()
|
||||
{
|
||||
// get sender address
|
||||
$headers = $this->message->headers();
|
||||
$to = rcube_mime::decode_address_list($headers['To'], null, false, null, true);
|
||||
$cc = rcube_mime::decode_address_list($headers['Cc'], null, false, null, true);
|
||||
$bcc = rcube_mime::decode_address_list($headers['Bcc'], null, false, null, true);
|
||||
|
||||
$recipients = array_unique(array_merge($to, $cc, $bcc));
|
||||
$recipients = array_diff($recipients, ['undisclosed-recipients:']);
|
||||
|
||||
return array_values($recipients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get original message body, to be encrypted/signed
|
||||
*
|
||||
* @return string Message body
|
||||
*/
|
||||
public function getOrigBody()
|
||||
{
|
||||
$_headers = $this->message->headers();
|
||||
$headers = [];
|
||||
|
||||
if (!empty($_headers['Content-Transfer-Encoding'])
|
||||
&& stripos($_headers['Content-Type'], 'multipart') === false
|
||||
) {
|
||||
$headers[] = 'Content-Transfer-Encoding: ' . $_headers['Content-Transfer-Encoding'];
|
||||
}
|
||||
$headers[] = 'Content-Type: ' . $_headers['Content-Type'];
|
||||
|
||||
return implode("\r\n", $headers) . "\r\n\r\n" . $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register signature attachment
|
||||
*
|
||||
* @param string Signature body
|
||||
* @param string Hash algorithm name
|
||||
*/
|
||||
public function addPGPSignature($body, $algorithm = null)
|
||||
{
|
||||
$this->signature = $body;
|
||||
$this->micalg = $algorithm;
|
||||
|
||||
// Reset Content-Type to be overwritten with valid boundary
|
||||
unset($this->headers['Content-Type']);
|
||||
unset($this->headers['Content-Transfer-Encoding']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register encrypted body
|
||||
*
|
||||
* @param string Encrypted body
|
||||
*/
|
||||
public function setPGPEncryptedBody($body)
|
||||
{
|
||||
$this->encrypted = $body;
|
||||
|
||||
// Reset Content-Type to be overwritten with valid boundary
|
||||
unset($this->headers['Content-Type']);
|
||||
unset($this->headers['Content-Transfer-Encoding']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the multipart message.
|
||||
*
|
||||
* @param array $params Build parameters that change the way the email
|
||||
* is built. Should be associative. See $_build_params.
|
||||
* @param resource $filename Output file where to save the message instead of
|
||||
* returning it
|
||||
* @param bool $skip_head True if you want to return/save only the message
|
||||
* without headers
|
||||
*
|
||||
* @return mixed The MIME message content string, null or PEAR error object
|
||||
*/
|
||||
public function get($params = null, $filename = null, $skip_head = false)
|
||||
{
|
||||
if (!empty($params)) {
|
||||
foreach ($params as $key => $value) {
|
||||
$this->build_params[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkParams();
|
||||
|
||||
if ($this->type == self::PGP_SIGNED) {
|
||||
$params = [
|
||||
'preamble' => "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)",
|
||||
'content_type' => "multipart/signed; protocol=\"application/pgp-signature\"",
|
||||
'eol' => $this->build_params['eol'],
|
||||
];
|
||||
|
||||
if ($this->micalg) {
|
||||
$params['content_type'] .= "; micalg=pgp-" . $this->micalg;
|
||||
}
|
||||
|
||||
$message = new Mail_mimePart('', $params);
|
||||
|
||||
if (!empty($this->body)) {
|
||||
$headers = $this->message->headers();
|
||||
$params = ['content_type' => $headers['Content-Type']];
|
||||
|
||||
if (!empty($headers['Content-Transfer-Encoding'])
|
||||
&& stripos($headers['Content-Type'], 'multipart') === false
|
||||
) {
|
||||
$params['encoding'] = $headers['Content-Transfer-Encoding'];
|
||||
|
||||
// For plain text body we have to decode it back, to prevent from
|
||||
// a double encoding issue (#8413)
|
||||
$this->body = rcube_mime::decode($this->body, $this->build_params['text_encoding']);
|
||||
}
|
||||
|
||||
$message->addSubpart($this->body, $params);
|
||||
}
|
||||
|
||||
if (!empty($this->signature)) {
|
||||
$message->addSubpart($this->signature, [
|
||||
'filename' => 'signature.asc',
|
||||
'content_type' => 'application/pgp-signature',
|
||||
'disposition' => 'attachment',
|
||||
'description' => 'OpenPGP digital signature',
|
||||
]);
|
||||
}
|
||||
}
|
||||
else if ($this->type == self::PGP_ENCRYPTED) {
|
||||
$params = [
|
||||
'preamble' => "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)",
|
||||
'content_type' => "multipart/encrypted; protocol=\"application/pgp-encrypted\"",
|
||||
'eol' => $this->build_params['eol'],
|
||||
];
|
||||
|
||||
$message = new Mail_mimePart('', $params);
|
||||
|
||||
$message->addSubpart('Version: 1', [
|
||||
'content_type' => 'application/pgp-encrypted',
|
||||
'description' => 'PGP/MIME version identification',
|
||||
]);
|
||||
|
||||
$message->addSubpart($this->encrypted, [
|
||||
'content_type' => 'application/octet-stream',
|
||||
'description' => 'PGP/MIME encrypted message',
|
||||
'disposition' => 'inline',
|
||||
'filename' => 'encrypted.asc',
|
||||
]);
|
||||
}
|
||||
|
||||
// Use saved boundary
|
||||
if (!empty($this->build_params['boundary'])) {
|
||||
$boundary = $this->build_params['boundary'];
|
||||
}
|
||||
else {
|
||||
$boundary = null;
|
||||
}
|
||||
|
||||
// Write output to file
|
||||
if ($filename) {
|
||||
// Append mimePart message headers and body into file
|
||||
$headers = $message->encodeToFile($filename, $boundary, $skip_head);
|
||||
|
||||
if ($this->isError($headers)) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
$this->headers = array_merge($this->headers, $headers);
|
||||
}
|
||||
else {
|
||||
$output = $message->encode($boundary, $skip_head);
|
||||
|
||||
if ($this->isError($output)) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
$this->headers = array_merge($this->headers, $output['headers']);
|
||||
}
|
||||
|
||||
// remember the boundary used, in case we'd handle headers() call later
|
||||
if (empty($boundary) && !empty($this->headers['Content-Type'])) {
|
||||
if (preg_match('/boundary="([^"]+)/', $this->headers['Content-Type'], $m)) {
|
||||
$this->build_params['boundary'] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $filename ? null : $output['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Content-Type and Content-Transfer-Encoding headers of the message
|
||||
*
|
||||
* @return array Headers array
|
||||
*/
|
||||
protected function contentHeaders()
|
||||
{
|
||||
$this->checkParams();
|
||||
|
||||
$eol = $this->build_params['eol'] ?: "\r\n";
|
||||
|
||||
// multipart message: and boundary
|
||||
if (!empty($this->build_params['boundary'])) {
|
||||
$boundary = $this->build_params['boundary'];
|
||||
}
|
||||
else if (!empty($this->headers['Content-Type'])
|
||||
&& preg_match('/boundary="([^"]+)"/', $this->headers['Content-Type'], $m)
|
||||
) {
|
||||
$boundary = $m[1];
|
||||
}
|
||||
else {
|
||||
$boundary = '=_' . md5(rand() . microtime());
|
||||
}
|
||||
|
||||
$this->build_params['boundary'] = $boundary;
|
||||
|
||||
if ($this->type == self::PGP_SIGNED) {
|
||||
$headers['Content-Type'] = "multipart/signed;$eol"
|
||||
." protocol=\"application/pgp-signature\";$eol"
|
||||
." boundary=\"$boundary\"";
|
||||
|
||||
if ($this->micalg) {
|
||||
$headers['Content-Type'] .= ";{$eol} micalg=pgp-" . $this->micalg;
|
||||
}
|
||||
}
|
||||
else if ($this->type == self::PGP_ENCRYPTED) {
|
||||
$headers['Content-Type'] = "multipart/encrypted;$eol"
|
||||
." protocol=\"application/pgp-encrypted\";$eol"
|
||||
." boundary=\"$boundary\"";
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| Signature class for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_signature
|
||||
{
|
||||
public $id;
|
||||
public $valid;
|
||||
public $fingerprint;
|
||||
public $created;
|
||||
public $expires;
|
||||
public $name;
|
||||
public $comment;
|
||||
public $email;
|
||||
|
||||
// Set it to true if signature is valid, but part of the message
|
||||
// was out of the signed block
|
||||
public $partial;
|
||||
|
||||
/**
|
||||
* Find key user id matching the email message sender
|
||||
*
|
||||
* @param enigma_engine $engine Enigma engine
|
||||
* @param rcube_message $message Message object
|
||||
* @param string $part_id Message part identifier
|
||||
*
|
||||
* @return string User identifier (name + email)
|
||||
*/
|
||||
public function get_sender($engine, $message, $part_id = null)
|
||||
{
|
||||
if (!$this->email) {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
if ($this->fingerprint && ($key = $engine->get_key($this->fingerprint))) {
|
||||
$from = $message->headers->from;
|
||||
$charset = $message->headers->charset;
|
||||
|
||||
// Get From: header from the parent part, if it's a forwarded message
|
||||
if ($part_id && strpos($part_id, '.') !== false) {
|
||||
$level = explode('.', $part_id);
|
||||
$parts = $message->mime_parts();
|
||||
|
||||
while (array_pop($level) !== null) {
|
||||
$parent = join('.', $level);
|
||||
if (!empty($parts[$parent]) && $parts[$parent]->mimetype == 'message/rfc822') {
|
||||
$from = $parts[$parent]->headers['from'];
|
||||
$charset = $parts[$parent]->charset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$from = rcube_mime::decode_address_list($from, 1, true, $charset);
|
||||
$from = (array) $from[1];
|
||||
|
||||
if (!empty($from)) {
|
||||
// Compare name and email
|
||||
foreach ($key->users as $user) {
|
||||
if ($user->name == $from['name'] && $user->email == $from['mailto']) {
|
||||
return sprintf('%s <%s>', $user->name, $user->email);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare only email
|
||||
foreach ($key->users as $user) {
|
||||
if ($user->email === $from['mailto']) {
|
||||
return sprintf('%s <%s>', $this->name, $user->email);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf('%s <%s>', $this->name, $this->email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| SubKey class for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_subkey
|
||||
{
|
||||
public $id;
|
||||
public $fingerprint;
|
||||
public $expires;
|
||||
public $created;
|
||||
public $revoked;
|
||||
public $has_private;
|
||||
public $algorithm;
|
||||
public $length;
|
||||
public $usage;
|
||||
|
||||
|
||||
/**
|
||||
* Converts internal ID to short ID
|
||||
* Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
|
||||
*
|
||||
* @return string Key ID
|
||||
*/
|
||||
function get_short_id()
|
||||
{
|
||||
// E.g. 04622F2089E037A5 => 89E037A5
|
||||
return enigma_key::format_id($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for formatted fingerprint
|
||||
*
|
||||
* @return string Formatted fingerprint
|
||||
*/
|
||||
function get_fingerprint()
|
||||
{
|
||||
return enigma_key::format_fingerprint($this->fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns human-readable name of the key's algorithm
|
||||
*
|
||||
* @return string Algorithm name
|
||||
*/
|
||||
function get_algorithm()
|
||||
{
|
||||
// http://tools.ietf.org/html/rfc4880#section-9.1
|
||||
switch ($this->algorithm) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
return 'RSA';
|
||||
case 16:
|
||||
case 20:
|
||||
return 'Elgamal';
|
||||
case 17:
|
||||
return 'DSA';
|
||||
case 18:
|
||||
return 'Elliptic Curve';
|
||||
case 19:
|
||||
return 'ECDSA';
|
||||
case 21:
|
||||
return 'Diffie-Hellman';
|
||||
case 22:
|
||||
return 'EdDSA';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the subkey has expired
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_expired()
|
||||
{
|
||||
$now = new DateTime('now');
|
||||
|
||||
return !empty($this->expires) && $this->expires < $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subkey creation date-time string
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function get_creation_date()
|
||||
{
|
||||
if (empty($this->created)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$date_format = rcube::get_instance()->config->get('date_format', 'Y-m-d');
|
||||
|
||||
return $this->created->format($date_format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subkey expiration date-time string
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function get_expiration_date()
|
||||
{
|
||||
if (empty($this->expires)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$date_format = rcube::get_instance()->config->get('date_format', 'Y-m-d');
|
||||
|
||||
return $this->expires->format($date_format);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-------------------------------------------------------------------------+
|
||||
| User ID class for the Enigma Plugin |
|
||||
| |
|
||||
| Copyright (C) The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class enigma_userid
|
||||
{
|
||||
public $revoked;
|
||||
public $valid;
|
||||
public $name;
|
||||
public $comment;
|
||||
public $email;
|
||||
}
|
||||
Reference in New Issue
Block a user