summaryrefslogtreecommitdiff
path: root/plugins/enigma/lib/Crypt/GPG/Engine.php
diff options
context:
space:
mode:
authorthomascube <thomas@roundcube.net>2011-01-12 15:47:44 +0000
committerthomascube <thomas@roundcube.net>2011-01-12 15:47:44 +0000
commit62a83b3bd5813885d964d9fd1509d52bb9c4f12c (patch)
tree241e4407b51747889e63cc2d26219301639c238d /plugins/enigma/lib/Crypt/GPG/Engine.php
parent28a4d4a49ebc4090155f97aeaf9a409f8e1549dc (diff)
Copying plugins to release branch
Diffstat (limited to 'plugins/enigma/lib/Crypt/GPG/Engine.php')
-rw-r--r--plugins/enigma/lib/Crypt/GPG/Engine.php1758
1 files changed, 1758 insertions, 0 deletions
diff --git a/plugins/enigma/lib/Crypt/GPG/Engine.php b/plugins/enigma/lib/Crypt/GPG/Engine.php
new file mode 100644
index 000000000..081be8e21
--- /dev/null
+++ b/plugins/enigma/lib/Crypt/GPG/Engine.php
@@ -0,0 +1,1758 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Crypt_GPG is a package to use GPG from PHP
+ *
+ * This file contains an engine that handles GPG subprocess control and I/O.
+ * PHP's process manipulation functions are used to handle the GPG subprocess.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @category Encryption
+ * @package Crypt_GPG
+ * @author Nathan Fredrickson <nathan@silverorange.com>
+ * @author Michael Gauthier <mike@silverorange.com>
+ * @copyright 2005-2010 silverorange
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Engine.php 302822 2010-08-26 17:30:57Z gauthierm $
+ * @link http://pear.php.net/package/Crypt_GPG
+ * @link http://www.gnupg.org/
+ */
+
+/**
+ * Crypt_GPG base class.
+ */
+require_once 'Crypt/GPG.php';
+
+/**
+ * GPG exception classes.
+ */
+require_once 'Crypt/GPG/Exceptions.php';
+
+/**
+ * Standard PEAR exception is used if GPG binary is not found.
+ */
+require_once 'PEAR/Exception.php';
+
+// {{{ class Crypt_GPG_Engine
+
+/**
+ * Native PHP Crypt_GPG I/O engine
+ *
+ * This class is used internally by Crypt_GPG and does not need be used
+ * directly. See the {@link Crypt_GPG} class for end-user API.
+ *
+ * This engine uses PHP's native process control functions to directly control
+ * the GPG process. The GPG executable is required to be on the system.
+ *
+ * All data is passed to the GPG subprocess using file descriptors. This is the
+ * most secure method of passing data to the GPG subprocess.
+ *
+ * @category Encryption
+ * @package Crypt_GPG
+ * @author Nathan Fredrickson <nathan@silverorange.com>
+ * @author Michael Gauthier <mike@silverorange.com>
+ * @copyright 2005-2010 silverorange
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @link http://pear.php.net/package/Crypt_GPG
+ * @link http://www.gnupg.org/
+ */
+class Crypt_GPG_Engine
+{
+ // {{{ constants
+
+ /**
+ * Size of data chunks that are sent to and retrieved from the IPC pipes.
+ *
+ * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192
+ * and buffers the rest so we might as well just read 8192.
+ *
+ * Using values other than 8192 also triggers PHP bugs.
+ *
+ * @see http://bugs.php.net/bug.php?id=35224
+ */
+ const CHUNK_SIZE = 8192;
+
+ /**
+ * Standard input file descriptor. This is used to pass data to the GPG
+ * process.
+ */
+ const FD_INPUT = 0;
+
+ /**
+ * Standard output file descriptor. This is used to receive normal output
+ * from the GPG process.
+ */
+ const FD_OUTPUT = 1;
+
+ /**
+ * Standard output file descriptor. This is used to receive error output
+ * from the GPG process.
+ */
+ const 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.
+ */
+ const FD_STATUS = 3;
+
+ /**
+ * Command input file descriptor. This is used for methods requiring
+ * passphrases.
+ */
+ const FD_COMMAND = 4;
+
+ /**
+ * Extra message input file descriptor. This is used for passing signed
+ * data when verifying a detached signature.
+ */
+ const FD_MESSAGE = 5;
+
+ /**
+ * Minimum version of GnuPG that is supported.
+ */
+ const MIN_VERSION = '1.0.2';
+
+ // }}}
+ // {{{ private class properties
+
+ /**
+ * Whether or not to use debugging mode
+ *
+ * When set to true, every GPG command is echoed before it is run. Sensitive
+ * data is always handled using pipes and is not specified as part of the
+ * command. As a result, sensitive data is never displayed when debug is
+ * enabled. Sensitive data includes private key data and passphrases.
+ *
+ * Debugging is off by default.
+ *
+ * @var boolean
+ * @see Crypt_GPG_Engine::__construct()
+ */
+ private $_debug = false;
+
+ /**
+ * Location of GPG binary
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::__construct()
+ * @see Crypt_GPG_Engine::_getBinary()
+ */
+ private $_binary = '';
+
+ /**
+ * Directory containing the GPG key files
+ *
+ * This property only contains the path when the <i>homedir</i> option
+ * is specified in the constructor.
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::__construct()
+ */
+ private $_homedir = '';
+
+ /**
+ * File path of the public keyring
+ *
+ * This property only contains the file path when the <i>public_keyring</i>
+ * option is specified in the constructor.
+ *
+ * If the specified file path starts with <kbd>~/</kbd>, the path is
+ * relative to the <i>homedir</i> if specified, otherwise to
+ * <kbd>~/.gnupg</kbd>.
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::__construct()
+ */
+ private $_publicKeyring = '';
+
+ /**
+ * File path of the private (secret) keyring
+ *
+ * This property only contains the file path when the <i>private_keyring</i>
+ * option is specified in the constructor.
+ *
+ * If the specified file path starts with <kbd>~/</kbd>, the path is
+ * relative to the <i>homedir</i> if specified, otherwise to
+ * <kbd>~/.gnupg</kbd>.
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::__construct()
+ */
+ private $_privateKeyring = '';
+
+ /**
+ * File path of the trust database
+ *
+ * This property only contains the file path when the <i>trust_db</i>
+ * option is specified in the constructor.
+ *
+ * If the specified file path starts with <kbd>~/</kbd>, the path is
+ * relative to the <i>homedir</i> if specified, otherwise to
+ * <kbd>~/.gnupg</kbd>.
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::__construct()
+ */
+ private $_trustDb = '';
+
+ /**
+ * Array of pipes used for communication with the GPG binary
+ *
+ * This is an array of file descriptor resources.
+ *
+ * @var array
+ */
+ private $_pipes = array();
+
+ /**
+ * Array of currently opened pipes
+ *
+ * This array is used to keep track of remaining opened pipes so they can
+ * be closed when the GPG subprocess is finished. This array is a subset of
+ * the {@link Crypt_GPG_Engine::$_pipes} array and contains opened file
+ * descriptor resources.
+ *
+ * @var array
+ * @see Crypt_GPG_Engine::_closePipe()
+ */
+ private $_openPipes = array();
+
+ /**
+ * A handle for the GPG process
+ *
+ * @var resource
+ */
+ private $_process = null;
+
+ /**
+ * Whether or not the operating system is Darwin (OS X)
+ *
+ * @var boolean
+ */
+ private $_isDarwin = false;
+
+ /**
+ * Commands to be sent to GPG's command input stream
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::sendCommand()
+ */
+ private $_commandBuffer = '';
+
+ /**
+ * Array of status line handlers
+ *
+ * @var array
+ * @see Crypt_GPG_Engine::addStatusHandler()
+ */
+ private $_statusHandlers = array();
+
+ /**
+ * Array of error line handlers
+ *
+ * @var array
+ * @see Crypt_GPG_Engine::addErrorHandler()
+ */
+ private $_errorHandlers = array();
+
+ /**
+ * The error code of the current operation
+ *
+ * @var integer
+ * @see Crypt_GPG_Engine::getErrorCode()
+ */
+ private $_errorCode = Crypt_GPG::ERROR_NONE;
+
+ /**
+ * File related to the error code of the current operation
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::getErrorFilename()
+ */
+ private $_errorFilename = '';
+
+ /**
+ * Key id related to the error code of the current operation
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::getErrorKeyId()
+ */
+ private $_errorkeyId = '';
+
+ /**
+ * The number of currently needed passphrases
+ *
+ * If this is not zero when the GPG command is completed, the error code is
+ * set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
+ *
+ * @var integer
+ */
+ private $_needPassphrase = 0;
+
+ /**
+ * The input source
+ *
+ * This is data to send to GPG. Either a string or a stream resource.
+ *
+ * @var string|resource
+ * @see Crypt_GPG_Engine::setInput()
+ */
+ private $_input = null;
+
+ /**
+ * The extra message input source
+ *
+ * Either a string or a stream resource.
+ *
+ * @var string|resource
+ * @see Crypt_GPG_Engine::setMessage()
+ */
+ private $_message = null;
+
+ /**
+ * The output location
+ *
+ * This is where the output from GPG is sent. Either a string or a stream
+ * resource.
+ *
+ * @var string|resource
+ * @see Crypt_GPG_Engine::setOutput()
+ */
+ private $_output = '';
+
+ /**
+ * The GPG operation to execute
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::setOperation()
+ */
+ private $_operation;
+
+ /**
+ * Arguments for the current operation
+ *
+ * @var array
+ * @see Crypt_GPG_Engine::setOperation()
+ */
+ private $_arguments = array();
+
+ /**
+ * The version number of the GPG binary
+ *
+ * @var string
+ * @see Crypt_GPG_Engine::getVersion()
+ */
+ private $_version = '';
+
+ /**
+ * Cached value indicating whether or not mbstring function overloading is
+ * on for strlen
+ *
+ * This is cached for optimal performance inside the I/O loop.
+ *
+ * @var boolean
+ * @see Crypt_GPG_Engine::_byteLength()
+ * @see Crypt_GPG_Engine::_byteSubstring()
+ */
+ private static $_mbStringOverload = null;
+
+ // }}}
+ // {{{ __construct()
+
+ /**
+ * Creates a new GPG engine
+ *
+ * Available options are:
+ *
+ * - <kbd>string homedir</kbd> - the directory where the GPG
+ * keyring files are stored. If not
+ * specified, Crypt_GPG uses the
+ * default of <kbd>~/.gnupg</kbd>.
+ * - <kbd>string publicKeyring</kbd> - the file path of the public
+ * keyring. Use this if the public
+ * keyring is not in the homedir, or
+ * if the keyring is in a directory
+ * not writable by the process
+ * invoking GPG (like Apache). Then
+ * you can specify the path to the
+ * keyring with this option
+ * (/foo/bar/pubring.gpg), and specify
+ * a writable directory (like /tmp)
+ * using the <i>homedir</i> option.
+ * - <kbd>string privateKeyring</kbd> - the file path of the private
+ * keyring. Use this if the private
+ * keyring is not in the homedir, or
+ * if the keyring is in a directory
+ * not writable by the process
+ * invoking GPG (like Apache). Then
+ * you can specify the path to the
+ * keyring with this option
+ * (/foo/bar/secring.gpg), and specify
+ * a writable directory (like /tmp)
+ * using the <i>homedir</i> option.
+ * - <kbd>string trustDb</kbd> - the file path of the web-of-trust
+ * database. Use this if the trust
+ * database is not in the homedir, or
+ * if the database is in a directory
+ * not writable by the process
+ * invoking GPG (like Apache). Then
+ * you can specify the path to the
+ * trust database with this option
+ * (/foo/bar/trustdb.gpg), and specify
+ * a writable directory (like /tmp)
+ * using the <i>homedir</i> option.
+ * - <kbd>string binary</kbd> - the location of the GPG binary. If
+ * not specified, the driver attempts
+ * to auto-detect the GPG binary
+ * location using a list of known
+ * default locations for the current
+ * operating system. The option
+ * <kbd>gpgBinary</kbd> is a
+ * deprecated alias for this option.
+ * - <kbd>boolean debug</kbd> - whether or not to use debug mode.
+ * When debug mode is on, all
+ * communication to and from the GPG
+ * subprocess is logged. This can be
+ * useful to diagnose errors when
+ * using Crypt_GPG.
+ *
+ * @param array $options optional. An array of options used to create the
+ * GPG object. All options are optional and are
+ * represented as key-value pairs.
+ *
+ * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
+ * and cannot be created. This can happen if <kbd>homedir</kbd> is
+ * not specified, Crypt_GPG is run as the web user, and the web
+ * user has no home directory. This exception is also thrown if any
+ * of the options <kbd>publicKeyring</kbd>,
+ * <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
+ * specified but the files do not exist or are are not readable.
+ * This can happen if the user running the Crypt_GPG process (for
+ * example, the Apache user) does not have permission to read the
+ * files.
+ *
+ * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
+ * if no <kbd>binary</kbd> is provided and no suitable binary could
+ * be found.
+ */
+ public function __construct(array $options = array())
+ {
+ $this->_isDarwin = (strncmp(strtoupper(PHP_OS), 'DARWIN', 6) === 0);
+
+ // populate mbstring overloading cache if not set
+ if (self::$_mbStringOverload === null) {
+ self::$_mbStringOverload = (extension_loaded('mbstring')
+ && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
+ }
+
+ // get homedir
+ if (array_key_exists('homedir', $options)) {
+ $this->_homedir = (string)$options['homedir'];
+ } else {
+ // note: this requires the package OS dep exclude 'windows'
+ $info = posix_getpwuid(posix_getuid());
+ $this->_homedir = $info['dir'].'/.gnupg';
+ }
+
+ // attempt to create homedir if it does not exist
+ if (!is_dir($this->_homedir)) {
+ if (@mkdir($this->_homedir, 0777, true)) {
+ // Set permissions on homedir. Parent directories are created
+ // with 0777, homedir is set to 0700.
+ chmod($this->_homedir, 0700);
+ } else {
+ throw new Crypt_GPG_FileException('The \'homedir\' "' .
+ $this->_homedir . '" is not readable or does not exist '.
+ 'and cannot be created. This can happen if \'homedir\' '.
+ 'is not specified in the Crypt_GPG options, Crypt_GPG is '.
+ 'run as the web user, and the web user has no home '.
+ 'directory.',
+ 0, $this->_homedir);
+ }
+ }
+
+ // get binary
+ if (array_key_exists('binary', $options)) {
+ $this->_binary = (string)$options['binary'];
+ } elseif (array_key_exists('gpgBinary', $options)) {
+ // deprecated alias
+ $this->_binary = (string)$options['gpgBinary'];
+ } else {
+ $this->_binary = $this->_getBinary();
+ }
+
+ if ($this->_binary == '' || !is_executable($this->_binary)) {
+ throw new PEAR_Exception('GPG binary not found. If you are sure '.
+ 'the GPG binary is installed, please specify the location of '.
+ 'the GPG binary using the \'binary\' driver option.');
+ }
+
+ /*
+ * Note:
+ *
+ * Normally, GnuPG expects keyrings to be in the homedir and expects
+ * to be able to write temporary files in the homedir. Sometimes,
+ * keyrings are not in the homedir, or location of the keyrings does
+ * not allow writing temporary files. In this case, the <i>homedir</i>
+ * option by itself is not enough to specify the keyrings because GnuPG
+ * can not write required temporary files. Additional options are
+ * provided so you can specify the location of the keyrings separately
+ * from the homedir.
+ */
+
+ // get public keyring
+ if (array_key_exists('publicKeyring', $options)) {
+ $this->_publicKeyring = (string)$options['publicKeyring'];
+ if (!is_readable($this->_publicKeyring)) {
+ throw new Crypt_GPG_FileException('The \'publicKeyring\' "' .
+ $this->_publicKeyring . '" does not exist or is ' .
+ 'not readable. Check the location and ensure the file ' .
+ 'permissions are correct.', 0, $this->_publicKeyring);
+ }
+ }
+
+ // get private keyring
+ if (array_key_exists('privateKeyring', $options)) {
+ $this->_privateKeyring = (string)$options['privateKeyring'];
+ if (!is_readable($this->_privateKeyring)) {
+ throw new Crypt_GPG_FileException('The \'privateKeyring\' "' .
+ $this->_privateKeyring . '" does not exist or is ' .
+ 'not readable. Check the location and ensure the file ' .
+ 'permissions are correct.', 0, $this->_privateKeyring);
+ }
+ }
+
+ // get trust database
+ if (array_key_exists('trustDb', $options)) {
+ $this->_trustDb = (string)$options['trustDb'];
+ if (!is_readable($this->_trustDb)) {
+ throw new Crypt_GPG_FileException('The \'trustDb\' "' .
+ $this->_trustDb . '" does not exist or is not readable. ' .
+ 'Check the location and ensure the file permissions are ' .
+ 'correct.', 0, $this->_trustDb);
+ }
+ }
+
+ if (array_key_exists('debug', $options)) {
+ $this->_debug = (boolean)$options['debug'];
+ }
+ }
+
+ // }}}
+ // {{{ __destruct()
+
+ /**
+ * Closes open GPG subprocesses when this object is destroyed
+ *
+ * Subprocesses should never be left open by this class unless there is
+ * an unknown error and unexpected script termination occurs.
+ */
+ public function __destruct()
+ {
+ $this->_closeSubprocess();
+ }
+
+ // }}}
+ // {{{ addErrorHandler()
+
+ /**
+ * Adds an error handler method
+ *
+ * The method is run every time a new error line is received from the GPG
+ * subprocess. The handler method must accept the error line to be handled
+ * as its first parameter.
+ *
+ * @param callback $callback the callback method to use.
+ * @param array $args optional. Additional arguments to pass as
+ * parameters to the callback method.
+ *
+ * @return void
+ */
+ public function addErrorHandler($callback, array $args = array())
+ {
+ $this->_errorHandlers[] = array(
+ 'callback' => $callback,
+ 'args' => $args
+ );
+ }
+
+ // }}}
+ // {{{ addStatusHandler()
+
+ /**
+ * Adds a status handler method
+ *
+ * The method is run every time a new status line is received from the
+ * GPG subprocess. The handler method must accept the status line to be
+ * handled as its first parameter.
+ *
+ * @param callback $callback the callback method to use.
+ * @param array $args optional. Additional arguments to pass as
+ * parameters to the callback method.
+ *
+ * @return void
+ */
+ public function addStatusHandler($callback, array $args = array())
+ {
+ $this->_statusHandlers[] = array(
+ 'callback' => $callback,
+ 'args' => $args
+ );
+ }
+
+ // }}}
+ // {{{ sendCommand()
+
+ /**
+ * Sends a command to the GPG subprocess over the command file-descriptor
+ * pipe
+ *
+ * @param string $command the command to send.
+ *
+ * @return void
+ *
+ * @sensitive $command
+ */
+ public function sendCommand($command)
+ {
+ if (array_key_exists(self::FD_COMMAND, $this->_openPipes)) {
+ $this->_commandBuffer .= $command . PHP_EOL;
+ }
+ }
+
+ // }}}
+ // {{{ reset()
+
+ /**
+ * Resets the GPG engine, preparing it for a new operation
+ *
+ * @return void
+ *
+ * @see Crypt_GPG_Engine::run()
+ * @see Crypt_GPG_Engine::setOperation()
+ */
+ public function reset()
+ {
+ $this->_operation = '';
+ $this->_arguments = array();
+ $this->_input = null;
+ $this->_message = null;
+ $this->_output = '';
+ $this->_errorCode = Crypt_GPG::ERROR_NONE;
+ $this->_needPassphrase = 0;
+ $this->_commandBuffer = '';
+
+ $this->_statusHandlers = array();
+ $this->_errorHandlers = array();
+
+ $this->addStatusHandler(array($this, '_handleErrorStatus'));
+ $this->addErrorHandler(array($this, '_handleErrorError'));
+
+ if ($this->_debug) {
+ $this->addStatusHandler(array($this, '_handleDebugStatus'));
+ $this->addErrorHandler(array($this, '_handleDebugError'));
+ }
+ }
+
+ // }}}
+ // {{{ run()
+
+ /**
+ * Runs the current GPG operation
+ *
+ * This creates and manages the GPG subprocess.
+ *
+ * The operation must be set with {@link Crypt_GPG_Engine::setOperation()}
+ * before this method is called.
+ *
+ * @return void
+ *
+ * @throws Crypt_GPG_InvalidOperationException if no operation is specified.
+ *
+ * @see Crypt_GPG_Engine::reset()
+ * @see Crypt_GPG_Engine::setOperation()
+ */
+ public function run()
+ {
+ if ($this->_operation === '') {
+ throw new Crypt_GPG_InvalidOperationException('No GPG operation ' .
+ 'specified. Use Crypt_GPG_Engine::setOperation() before ' .
+ 'calling Crypt_GPG_Engine::run().');
+ }
+
+ $this->_openSubprocess();
+ $this->_process();
+ $this->_closeSubprocess();
+ }
+
+ // }}}
+ // {{{ getErrorCode()
+
+ /**
+ * Gets the error code of the last executed operation
+ *
+ * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
+ * been executed.
+ *
+ * @return integer the error code of the last executed operation.
+ */
+ public function getErrorCode()
+ {
+ return $this->_errorCode;
+ }
+
+ // }}}
+ // {{{ getErrorFilename()
+
+ /**
+ * Gets the file related to the error code of the last executed operation
+ *
+ * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
+ * been executed. If there is no file related to the error, an empty string
+ * is returned.
+ *
+ * @return string the file related to the error code of the last executed
+ * operation.
+ */
+ public function getErrorFilename()
+ {
+ return $this->_errorFilename;
+ }
+
+ // }}}
+ // {{{ getErrorKeyId()
+
+ /**
+ * Gets the key id related to the error code of the last executed operation
+ *
+ * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
+ * been executed. If there is no key id related to the error, an empty
+ * string is returned.
+ *
+ * @return string the key id related to the error code of the last executed
+ * operation.
+ */
+ public function getErrorKeyId()
+ {
+ return $this->_errorKeyId;
+ }
+
+ // }}}
+ // {{{ setInput()
+
+ /**
+ * Sets the input source for the current GPG operation
+ *
+ * @param string|resource &$input either a reference to the string
+ * containing the input data or an open
+ * stream resource containing the input
+ * data.
+ *
+ * @return void
+ */
+ public function setInput(&$input)
+ {
+ $this->_input =& $input;
+ }
+
+ // }}}
+ // {{{ setMessage()
+
+ /**
+ * Sets the message source for the current GPG operation
+ *
+ * Detached signature data should be specified here.
+ *
+ * @param string|resource &$message either a reference to the string
+ * containing the message data or an open
+ * stream resource containing the message
+ * data.
+ *
+ * @return void
+ */
+ public function setMessage(&$message)
+ {
+ $this->_message =& $message;
+ }
+
+ // }}}
+ // {{{ setOutput()
+
+ /**
+ * Sets the output destination for the current GPG operation
+ *
+ * @param string|resource &$output either a reference to the string in
+ * which to store GPG output or an open
+ * stream resource to which the output data
+ * should be written.
+ *
+ * @return void
+ */
+ public function setOutput(&$output)
+ {
+ $this->_output =& $output;
+ }
+
+ // }}}
+ // {{{ setOperation()
+
+ /**
+ * Sets the operation to perform
+ *
+ * @param string $operation the operation to perform. This should be one
+ * of GPG's operations. For example,
+ * <kbd>--encrypt</kbd>, <kbd>--decrypt</kbd>,
+ * <kbd>--sign</kbd>, etc.
+ * @param array $arguments optional. Additional arguments for the GPG
+ * subprocess. See the GPG manual for specific
+ * values.
+ *
+ * @return void
+ *
+ * @see Crypt_GPG_Engine::reset()
+ * @see Crypt_GPG_Engine::run()
+ */
+ public function setOperation($operation, array $arguments = array())
+ {
+ $this->_operation = $operation;
+ $this->_arguments = $arguments;
+ }
+
+ // }}}
+ // {{{ getVersion()
+
+ /**
+ * Gets the version of the GnuPG binary
+ *
+ * @return string a version number string containing the version of GnuPG
+ * being used. This value is suitable to use with PHP's
+ * version_compare() function.
+ *
+ * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
+ * Use the <kbd>debug</kbd> option and file a bug report if these
+ * exceptions occur.
+ *
+ * @throws Crypt_GPG_UnsupportedException if the provided binary is not
+ * GnuPG or if the GnuPG version is less than 1.0.2.
+ */
+ public function getVersion()
+ {
+ if ($this->_version == '') {
+
+ $options = array(
+ 'homedir' => $this->_homedir,
+ 'binary' => $this->_binary,
+ 'debug' => $this->_debug
+ );
+
+ $engine = new self($options);
+ $info = '';
+
+ // Set a garbage version so we do not end up looking up the version
+ // recursively.
+ $engine->_version = '1.0.0';
+
+ $engine->reset();
+ $engine->setOutput($info);
+ $engine->setOperation('--version');
+ $engine->run();
+
+ $code = $this->getErrorCode();
+
+ if ($code !== Crypt_GPG::ERROR_NONE) {
+ throw new Crypt_GPG_Exception(
+ 'Unknown error getting GnuPG version information. Please ' .
+ 'use the \'debug\' option when creating the Crypt_GPG ' .
+ 'object, and file a bug report at ' . Crypt_GPG::BUG_URI,
+ $code);
+ }
+
+ $matches = array();
+ $expression = '/gpg \(GnuPG\) (\S+)/';
+
+ if (preg_match($expression, $info, $matches) === 1) {
+ $this->_version = $matches[1];
+ } else {
+ throw new Crypt_GPG_Exception(
+ 'No GnuPG version information provided by the binary "' .
+ $this->_binary . '". Are you sure it is GnuPG?');
+ }
+
+ if (version_compare($this->_version, self::MIN_VERSION, 'lt')) {
+ throw new Crypt_GPG_Exception(
+ 'The version of GnuPG being used (' . $this->_version .
+ ') is not supported by Crypt_GPG. The minimum version ' .
+ 'required by Crypt_GPG is ' . self::MIN_VERSION);
+ }
+ }
+
+
+ return $this->_version;
+ }
+
+ // }}}
+ // {{{ _handleErrorStatus()
+
+ /**
+ * Handles error values in the status output from GPG
+ *
+ * This method is responsible for setting the
+ * {@link Crypt_GPG_Engine::$_errorCode}. See <b>doc/DETAILS</b> in the
+ * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
+ * information on GPG's status output.
+ *
+ * @param string $line the status line to handle.
+ *
+ * @return void
+ */
+ private function _handleErrorStatus($line)
+ {
+ $tokens = explode(' ', $line);
+ switch ($tokens[0]) {
+ case 'BAD_PASSPHRASE':
+ $this->_errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
+ break;
+
+ case 'MISSING_PASSPHRASE':
+ $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
+ break;
+
+ case 'NODATA':
+ $this->_errorCode = Crypt_GPG::ERROR_NO_DATA;
+ break;
+
+ case 'DELETE_PROBLEM':
+ if ($tokens[1] == '1') {
+ $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
+ break;
+ } elseif ($tokens[1] == '2') {
+ $this->_errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
+ break;
+ }
+ break;
+
+ case 'IMPORT_RES':
+ if ($tokens[12] > 0) {
+ $this->_errorCode = Crypt_GPG::ERROR_DUPLICATE_KEY;
+ }
+ break;
+
+ case 'NO_PUBKEY':
+ case 'NO_SECKEY':
+ $this->_errorKeyId = $tokens[1];
+ $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
+ break;
+
+ case 'NEED_PASSPHRASE':
+ $this->_needPassphrase++;
+ break;
+
+ case 'GOOD_PASSPHRASE':
+ $this->_needPassphrase--;
+ break;
+
+ case 'EXPSIG':
+ case 'EXPKEYSIG':
+ case 'REVKEYSIG':
+ case 'BADSIG':
+ $this->_errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
+ break;
+
+ }
+ }
+
+ // }}}
+ // {{{ _handleErrorError()
+
+ /**
+ * Handles error values in the error output from GPG
+ *
+ * This method is responsible for setting the
+ * {@link Crypt_GPG_Engine::$_errorCode}.
+ *
+ * @param string $line the error line to handle.
+ *
+ * @return void
+ */
+ private function _handleErrorError($line)
+ {
+ if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
+ $pattern = '/no valid OpenPGP data found/';
+ if (preg_match($pattern, $line) === 1) {
+ $this->_errorCode = Crypt_GPG::ERROR_NO_DATA;
+ }
+ }
+
+ if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
+ $pattern = '/No secret key|secret key not available/';
+ if (preg_match($pattern, $line) === 1) {
+ $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
+ }
+ }
+
+ if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
+ $pattern = '/No public key|public key not found/';
+ if (preg_match($pattern, $line) === 1) {
+ $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
+ }
+ }
+
+ if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
+ $matches = array();
+ $pattern = '/can\'t (?:access|open) `(.*?)\'/';
+ if (preg_match($pattern, $line, $matches) === 1) {
+ $this->_errorFilename = $matches[1];
+ $this->_errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _handleDebugStatus()
+
+ /**
+ * Displays debug output for status lines
+ *
+ * @param string $line the status line to handle.
+ *
+ * @return void
+ */
+ private function _handleDebugStatus($line)
+ {
+ $this->_debug('STATUS: ' . $line);
+ }
+
+ // }}}
+ // {{{ _handleDebugError()
+
+ /**
+ * Displays debug output for error lines
+ *
+ * @param string $line the error line to handle.
+ *
+ * @return void
+ */
+ private function _handleDebugError($line)
+ {
+ $this->_debug('ERROR: ' . $line);
+ }
+
+ // }}}
+ // {{{ _process()
+
+ /**
+ * Performs internal streaming operations for the subprocess using either
+ * strings or streams as input / output points
+ *
+ * This is the main I/O loop for streaming to and from the GPG subprocess.
+ *
+ * The implementation of this method is verbose mainly for performance
+ * reasons. Adding streams to a lookup array and looping the array inside
+ * the main I/O loop would be siginficantly slower for large streams.
+ *
+ * @return void
+ *
+ * @throws Crypt_GPG_Exception if there is an error selecting streams for
+ * reading or writing. If this occurs, please file a bug report at
+ * http://pear.php.net/bugs/report.php?package=Crypt_GPG.
+ */
+ private function _process()
+ {
+ $this->_debug('BEGIN PROCESSING');
+
+ $this->_commandBuffer = ''; // buffers input to GPG
+ $messageBuffer = ''; // buffers input to GPG
+ $inputBuffer = ''; // buffers input to GPG
+ $outputBuffer = ''; // buffers output from GPG
+ $statusBuffer = ''; // buffers output from GPG
+ $errorBuffer = ''; // buffers output from GPG
+ $inputComplete = false; // input stream is completely buffered
+ $messageComplete = false; // message stream is completely buffered
+
+ if (is_string($this->_input)) {
+ $inputBuffer = $this->_input;
+ $inputComplete = true;
+ }
+
+ if (is_string($this->_message)) {
+ $messageBuffer = $this->_message;
+ $messageComplete = true;
+ }
+
+ if (is_string($this->_output)) {
+ $outputBuffer =& $this->_output;
+ }
+
+ // convenience variables
+ $fdInput = $this->_pipes[self::FD_INPUT];
+ $fdOutput = $this->_pipes[self::FD_OUTPUT];
+ $fdError = $this->_pipes[self::FD_ERROR];
+ $fdStatus = $this->_pipes[self::FD_STATUS];
+ $fdCommand = $this->_pipes[self::FD_COMMAND];
+ $fdMessage = $this->_pipes[self::FD_MESSAGE];
+
+ while (true) {
+
+ $inputStreams = array();
+ $outputStreams = array();
+ $exceptionStreams = array();
+
+ // set up input streams
+ if (is_resource($this->_input) && !$inputComplete) {
+ if (feof($this->_input)) {
+ $inputComplete = true;
+ } else {
+ $inputStreams[] = $this->_input;
+ }
+ }
+
+ // close GPG input pipe if there is no more data
+ if ($inputBuffer == '' && $inputComplete) {
+ $this->_debug('=> closing GPG input pipe');
+ $this->_closePipe(self::FD_INPUT);
+ }
+
+ if (is_resource($this->_message) && !$messageComplete) {
+ if (feof($this->_message)) {
+ $messageComplete = true;
+ } else {
+ $inputStreams[] = $this->_message;
+ }
+ }
+
+ // close GPG message pipe if there is no more data
+ if ($messageBuffer == '' && $messageComplete) {
+ $this->_debug('=> closing GPG message pipe');
+ $this->_closePipe(self::FD_MESSAGE);
+ }
+
+ if (!feof($fdOutput)) {
+ $inputStreams[] = $fdOutput;
+ }
+
+ if (!feof($fdStatus)) {
+ $inputStreams[] = $fdStatus;
+ }
+
+ if (!feof($fdError)) {
+ $inputStreams[] = $fdError;
+ }
+
+ // set up output streams
+ if ($outputBuffer != '' && is_resource($this->_output)) {
+ $outputStreams[] = $this->_output;
+ }
+
+ if ($this->_commandBuffer != '') {
+ $outputStreams[] = $fdCommand;
+ }
+
+ if ($messageBuffer != '') {
+ $outputStreams[] = $fdMessage;
+ }
+
+ if ($inputBuffer != '') {
+ $outputStreams[] = $fdInput;
+ }
+
+ // no streams left to read or write, we're all done
+ if (count($inputStreams) === 0 && count($outputStreams) === 0) {
+ break;
+ }
+
+ $this->_debug('selecting streams');
+
+ $ready = stream_select(
+ $inputStreams,
+ $outputStreams,
+ $exceptionStreams,
+ null
+ );
+
+ $this->_debug('=> got ' . $ready);
+
+ if ($ready === false) {
+ throw new Crypt_GPG_Exception(
+ 'Error selecting stream for communication with GPG ' .
+ 'subprocess. Please file a bug report at: ' .
+ 'http://pear.php.net/bugs/report.php?package=Crypt_GPG');
+ }
+
+ if ($ready === 0) {
+ throw new Crypt_GPG_Exception(
+ 'stream_select() returned 0. This can not happen! Please ' .
+ 'file a bug report at: ' .
+ 'http://pear.php.net/bugs/report.php?package=Crypt_GPG');
+ }
+
+ // write input (to GPG)
+ if (in_array($fdInput, $outputStreams)) {
+ $this->_debug('GPG is ready for input');
+
+ $chunk = self::_byteSubstring(
+ $inputBuffer,
+ 0,
+ self::CHUNK_SIZE
+ );
+
+ $length = self::_byteLength($chunk);
+
+ $this->_debug(
+ '=> about to write ' . $length . ' bytes to GPG input'
+ );
+
+ $length = fwrite($fdInput, $chunk, $length);
+
+ $this->_debug('=> wrote ' . $length . ' bytes');
+
+ $inputBuffer = self::_byteSubstring(
+ $inputBuffer,
+ $length
+ );
+ }
+
+ // read input (from PHP stream)
+ if (in_array($this->_input, $inputStreams)) {
+ $this->_debug('input stream is ready for reading');
+ $this->_debug(
+ '=> about to read ' . self::CHUNK_SIZE .
+ ' bytes from input stream'
+ );
+
+ $chunk = fread($this->_input, self::CHUNK_SIZE);
+ $length = self::_byteLength($chunk);
+ $inputBuffer .= $chunk;
+
+ $this->_debug('=> read ' . $length . ' bytes');
+ }
+
+ // write message (to GPG)
+ if (in_array($fdMessage, $outputStreams)) {
+ $this->_debug('GPG is ready for message data');
+
+ $chunk = self::_byteSubstring(
+ $messageBuffer,
+ 0,
+ self::CHUNK_SIZE
+ );
+
+ $length = self::_byteLength($chunk);
+
+ $this->_debug(
+ '=> about to write ' . $length . ' bytes to GPG message'
+ );
+
+ $length = fwrite($fdMessage, $chunk, $length);
+ $this->_debug('=> wrote ' . $length . ' bytes');
+
+ $messageBuffer = self::_byteSubstring($messageBuffer, $length);
+ }
+
+ // read message (from PHP stream)
+ if (in_array($this->_message, $inputStreams)) {
+ $this->_debug('message stream is ready for reading');
+ $this->_debug(
+ '=> about to read ' . self::CHUNK_SIZE .
+ ' bytes from message stream'
+ );
+
+ $chunk = fread($this->_message, self::CHUNK_SIZE);
+ $length = self::_byteLength($chunk);
+ $messageBuffer .= $chunk;
+
+ $this->_debug('=> read ' . $length . ' bytes');
+ }
+
+ // read output (from GPG)
+ if (in_array($fdOutput, $inputStreams)) {
+ $this->_debug('GPG output stream ready for reading');
+ $this->_debug(
+ '=> about to read ' . self::CHUNK_SIZE .
+ ' bytes from GPG output'
+ );
+
+ $chunk = fread($fdOutput, self::CHUNK_SIZE);
+ $length = self::_byteLength($chunk);
+ $outputBuffer .= $chunk;
+
+ $this->_debug('=> read ' . $length . ' bytes');
+ }
+
+ // write output (to PHP stream)
+ if (in_array($this->_output, $outputStreams)) {
+ $this->_debug('output stream is ready for data');
+
+ $chunk = self::_byteSubstring(
+ $outputBuffer,
+ 0,
+ self::CHUNK_SIZE
+ );
+
+ $length = self::_byteLength($chunk);
+
+ $this->_debug(
+ '=> about to write ' . $length . ' bytes to output stream'
+ );
+
+ $length = fwrite($this->_output, $chunk, $length);
+
+ $this->_debug('=> wrote ' . $length . ' bytes');
+
+ $outputBuffer = self::_byteSubstring($outputBuffer, $length);
+ }
+
+ // read error (from GPG)
+ if (in_array($fdError, $inputStreams)) {
+ $this->_debug('GPG error stream ready for reading');
+ $this->_debug(
+ '=> about to read ' . self::CHUNK_SIZE .
+ ' bytes from GPG error'
+ );
+
+ $chunk = fread($fdError, self::CHUNK_SIZE);
+ $length = self::_byteLength($chunk);
+ $errorBuffer .= $chunk;
+
+ $this->_debug('=> read ' . $length . ' bytes');
+
+ // pass lines to error handlers
+ while (($pos = strpos($errorBuffer, PHP_EOL)) !== false) {
+ $line = self::_byteSubstring($errorBuffer, 0, $pos);
+ foreach ($this->_errorHandlers as $handler) {
+ array_unshift($handler['args'], $line);
+ call_user_func_array(
+ $handler['callback'],
+ $handler['args']
+ );
+
+ array_shift($handler['args']);
+ }
+ $errorBuffer = self::_byteSubString(
+ $errorBuffer,
+ $pos + self::_byteLength(PHP_EOL)
+ );
+ }
+ }
+
+ // read status (from GPG)
+ if (in_array($fdStatus, $inputStreams)) {
+ $this->_debug('GPG status stream ready for reading');
+ $this->_debug(
+ '=> about to read ' . self::CHUNK_SIZE .
+ ' bytes from GPG status'
+ );
+
+ $chunk = fread($fdStatus, self::CHUNK_SIZE);
+ $length = self::_byteLength($chunk);
+ $statusBuffer .= $chunk;
+
+ $this->_debug('=> read ' . $length . ' bytes');
+
+ // pass lines to status handlers
+ while (($pos = strpos($statusBuffer, PHP_EOL)) !== false) {
+ $line = self::_byteSubstring($statusBuffer, 0, $pos);
+ // only pass lines beginning with magic prefix
+ if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') {
+ $line = self::_byteSubstring($line, 9);
+ foreach ($this->_statusHandlers as $handler) {
+ array_unshift($handler['args'], $line);
+ call_user_func_array(
+ $handler['callback'],
+ $handler['args']
+ );
+
+ array_shift($handler['args']);
+ }
+ }
+ $statusBuffer = self::_byteSubString(
+ $statusBuffer,
+ $pos + self::_byteLength(PHP_EOL)
+ );
+ }
+ }
+
+ // write command (to GPG)
+ if (in_array($fdCommand, $outputStreams)) {
+ $this->_debug('GPG is ready for command data');
+
+ // send commands
+ $chunk = self::_byteSubstring(
+ $this->_commandBuffer,
+ 0,
+ self::CHUNK_SIZE
+ );
+
+ $length = self::_byteLength($chunk);
+
+ $this->_debug(
+ '=> about to write ' . $length . ' bytes to GPG command'
+ );
+
+ $length = fwrite($fdCommand, $chunk, $length);
+
+ $this->_debug('=> wrote ' . $length);
+
+ $this->_commandBuffer = self::_byteSubstring(
+ $this->_commandBuffer,
+ $length
+ );
+ }
+
+ } // end loop while streams are open
+
+ $this->_debug('END PROCESSING');
+ }
+
+ // }}}
+ // {{{ _openSubprocess()
+
+ /**
+ * Opens an internal GPG subprocess for the current operation
+ *
+ * Opens a GPG subprocess, then connects the subprocess to some pipes. Sets
+ * the private class property {@link Crypt_GPG_Engine::$_process} to
+ * the new subprocess.
+ *
+ * @return void
+ *
+ * @throws Crypt_GPG_OpenSubprocessException if the subprocess could not be
+ * opened.
+ *
+ * @see Crypt_GPG_Engine::setOperation()
+ * @see Crypt_GPG_Engine::_closeSubprocess()
+ * @see Crypt_GPG_Engine::$_process
+ */
+ private function _openSubprocess()
+ {
+ $version = $this->getVersion();
+
+ $env = $_ENV;
+
+ // Newer versions of GnuPG return localized results. Crypt_GPG only
+ // works with English, so set the locale to 'C' for the subprocess.
+ $env['LC_ALL'] = 'C';
+
+ $commandLine = $this->_binary;
+
+ $defaultArguments = array(
+ '--status-fd ' . escapeshellarg(self::FD_STATUS),
+ '--command-fd ' . escapeshellarg(self::FD_COMMAND),
+ '--no-secmem-warning',
+ '--no-tty',
+ '--no-default-keyring', // ignored if keying files are not specified
+ '--no-options' // prevent creation of ~/.gnupg directory
+ );
+
+ if (version_compare($version, '1.0.7', 'ge')) {
+ if (version_compare($version, '2.0.0', 'lt')) {
+ $defaultArguments[] = '--no-use-agent';
+ }
+ $defaultArguments[] = '--no-permission-warning';
+ }
+
+ if (version_compare($version, '1.4.2', 'ge')) {
+ $defaultArguments[] = '--exit-on-status-write-error';
+ }
+
+ if (version_compare($version, '1.3.2', 'ge')) {
+ $defaultArguments[] = '--trust-model always';
+ } else {
+ $defaultArguments[] = '--always-trust';
+ }
+
+ $arguments = array_merge($defaultArguments, $this->_arguments);
+
+ if ($this->_homedir) {
+ $arguments[] = '--homedir ' . escapeshellarg($this->_homedir);
+
+ // the random seed file makes subsequent actions faster so only
+ // disable it if we have to.
+ if (!is_writeable($this->_homedir)) {
+ $arguments[] = '--no-random-seed-file';
+ }
+ }
+
+ if ($this->_publicKeyring) {
+ $arguments[] = '--keyring ' . escapeshellarg($this->_publicKeyring);
+ }
+
+ if ($this->_privateKeyring) {
+ $arguments[] = '--secret-keyring ' .
+ escapeshellarg($this->_privateKeyring);
+ }
+
+ if ($this->_trustDb) {
+ $arguments[] = '--trustdb-name ' . escapeshellarg($this->_trustDb);
+ }
+
+ $commandLine .= ' ' . implode(' ', $arguments) . ' ' .
+ $this->_operation;
+
+ // Binary operations will not work on Windows with PHP < 5.2.6. This is
+ // in case stream_select() ever works on Windows.
+ $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
+ $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';
+
+ $descriptorSpec = array(
+ self::FD_INPUT => array('pipe', $rb), // stdin
+ self::FD_OUTPUT => array('pipe', $wb), // stdout
+ self::FD_ERROR => array('pipe', $wb), // stderr
+ self::FD_STATUS => array('pipe', $wb), // status
+ self::FD_COMMAND => array('pipe', $rb), // command
+ self::FD_MESSAGE => array('pipe', $rb) // message
+ );
+
+ $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:');
+ $this->_debug($commandLine);
+
+ $this->_process = proc_open(
+ $commandLine,
+ $descriptorSpec,
+ $this->_pipes,
+ null,
+ $env,
+ array('binary_pipes' => true)
+ );
+
+ if (!is_resource($this->_process)) {
+ throw new Crypt_GPG_OpenSubprocessException(
+ 'Unable to open GPG subprocess.', 0, $commandLine);
+ }
+
+ $this->_openPipes = $this->_pipes;
+ $this->_errorCode = Crypt_GPG::ERROR_NONE;
+ }
+
+ // }}}
+ // {{{ _closeSubprocess()
+
+ /**
+ * Closes a the internal GPG subprocess
+ *
+ * Closes the internal GPG subprocess. Sets the private class property
+ * {@link Crypt_GPG_Engine::$_process} to null.
+ *
+ * @return void
+ *
+ * @see Crypt_GPG_Engine::_openSubprocess()
+ * @see Crypt_GPG_Engine::$_process
+ */
+ private function _closeSubprocess()
+ {
+ if (is_resource($this->_process)) {
+ $this->_debug('CLOSING SUBPROCESS');
+
+ // close remaining open pipes
+ foreach (array_keys($this->_openPipes) as $pipeNumber) {
+ $this->_closePipe($pipeNumber);
+ }
+
+ $exitCode = proc_close($this->_process);
+
+ if ($exitCode != 0) {
+ $this->_debug(
+ '=> subprocess returned an unexpected exit code: ' .
+ $exitCode
+ );
+
+ if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
+ if ($this->_needPassphrase > 0) {
+ $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
+ } else {
+ $this->_errorCode = Crypt_GPG::ERROR_UNKNOWN;
+ }
+ }
+ }
+
+ $this->_process = null;
+ $this->_pipes = array();
+ }
+ }
+
+ // }}}
+ // {{{ _closePipe()
+
+ /**
+ * Closes an opened pipe used to communicate with the GPG subprocess
+ *
+ * If the pipe is already closed, it is ignored. If the pipe is open, it
+ * is flushed and then closed.
+ *
+ * @param integer $pipeNumber the file descriptor number of the pipe to
+ * close.
+ *
+ * @return void
+ */
+ private function _closePipe($pipeNumber)
+ {
+ $pipeNumber = intval($pipeNumber);
+ if (array_key_exists($pipeNumber, $this->_openPipes)) {
+ fflush($this->_openPipes[$pipeNumber]);
+ fclose($this->_openPipes[$pipeNumber]);
+ unset($this->_openPipes[$pipeNumber]);
+ }
+ }
+
+ // }}}
+ // {{{ _getBinary()
+
+ /**
+ * Gets the name of the GPG binary for the current operating system
+ *
+ * This method is called if the '<kbd>binary</kbd>' option is <i>not</i>
+ * specified when creating this driver.
+ *
+ * @return string the name of the GPG binary for the current operating
+ * system. If no suitable binary could be found, an empty
+ * string is returned.
+ */
+ private function _getBinary()
+ {
+ $binary = '';
+
+ if ($this->_isDarwin) {
+ $binaryFiles = array(
+ '/opt/local/bin/gpg', // MacPorts
+ '/usr/local/bin/gpg', // Mac GPG
+ '/sw/bin/gpg', // Fink
+ '/usr/bin/gpg'
+ );
+ } else {
+ $binaryFiles = array(
+ '/usr/bin/gpg',
+ '/usr/local/bin/gpg'
+ );
+ }
+
+ foreach ($binaryFiles as $binaryFile) {
+ if (is_executable($binaryFile)) {
+ $binary = $binaryFile;
+ break;
+ }
+ }
+
+ return $binary;
+ }
+
+ // }}}
+ // {{{ _debug()
+
+ /**
+ * Displays debug text if debugging is turned on
+ *
+ * Debugging text is prepended with a debug identifier and echoed to stdout.
+ *
+ * @param string $text the debugging text to display.
+ *
+ * @return void
+ */
+ private function _debug($text)
+ {
+ if ($this->_debug) {
+ if (array_key_exists('SHELL', $_ENV)) {
+ foreach (explode(PHP_EOL, $text) as $line) {
+ echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
+ }
+ } else {
+ // running on a web server, format debug output nicely
+ foreach (explode(PHP_EOL, $text) as $line) {
+ echo "Crypt_GPG DEBUG: <strong>", $line,
+ '</strong><br />', PHP_EOL;
+ }
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _byteLength()
+
+ /**
+ * Gets the length of a string in bytes even if mbstring function
+ * overloading is turned on
+ *
+ * This is used for stream-based communication with the GPG subprocess.
+ *
+ * @param string $string the string for which to get the length.
+ *
+ * @return integer the length of the string in bytes.
+ *
+ * @see Crypt_GPG_Engine::$_mbStringOverload
+ */
+ private static function _byteLength($string)
+ {
+ if (self::$_mbStringOverload) {
+ return mb_strlen($string, '8bit');
+ }
+
+ return strlen((binary)$string);
+ }
+
+ // }}}
+ // {{{ _byteSubstring()
+
+ /**
+ * Gets the substring of a string in bytes even if mbstring function
+ * overloading is turned on
+ *
+ * This is used for stream-based communication with the GPG subprocess.
+ *
+ * @param string $string the input string.
+ * @param integer $start the starting point at which to get the substring.
+ * @param integer $length optional. The length of the substring.
+ *
+ * @return string the extracted part of the string. Unlike the default PHP
+ * <kbd>substr()</kbd> function, the returned value is
+ * always a string and never false.
+ *
+ * @see Crypt_GPG_Engine::$_mbStringOverload
+ */
+ private static function _byteSubstring($string, $start, $length = null)
+ {
+ if (self::$_mbStringOverload) {
+ if ($length === null) {
+ return mb_substr(
+ $string,
+ $start,
+ self::_byteLength($string) - $start, '8bit'
+ );
+ }
+
+ return mb_substr($string, $start, $length, '8bit');
+ }
+
+ if ($length === null) {
+ return (string)substr((binary)$string, $start);
+ }
+
+ return (string)substr((binary)$string, $start, $length);
+ }
+
+ // }}}
+}
+
+// }}}
+
+?>