| | |
| | | * @package Crypt_GPG |
| | | * @author Nathan Fredrickson <nathan@silverorange.com> |
| | | * @author Michael Gauthier <mike@silverorange.com> |
| | | * @copyright 2005-2010 silverorange |
| | | * @copyright 2005-2013 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 $ |
| | | * @version CVS: $Id$ |
| | | * @link http://pear.php.net/package/Crypt_GPG |
| | | * @link http://www.gnupg.org/ |
| | | */ |
| | |
| | | * GPG exception classes. |
| | | */ |
| | | require_once 'Crypt/GPG/Exceptions.php'; |
| | | |
| | | /** |
| | | * Byte string operations. |
| | | */ |
| | | require_once 'Crypt/GPG/ByteUtils.php'; |
| | | |
| | | /** |
| | | * Process control methods. |
| | | */ |
| | | require_once 'Crypt/GPG/ProcessControl.php'; |
| | | |
| | | /** |
| | | * Standard PEAR exception is used if GPG binary is not found. |
| | |
| | | * @package Crypt_GPG |
| | | * @author Nathan Fredrickson <nathan@silverorange.com> |
| | | * @author Michael Gauthier <mike@silverorange.com> |
| | | * @copyright 2005-2010 silverorange |
| | | * @copyright 2005-2013 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/ |
| | |
| | | private $_binary = ''; |
| | | |
| | | /** |
| | | * Location of GnuPG agent binary |
| | | * |
| | | * Only used for GnuPG 2.x |
| | | * |
| | | * @var string |
| | | * @see Crypt_GPG_Engine::__construct() |
| | | * @see Crypt_GPG_Engine::_getAgent() |
| | | */ |
| | | private $_agent = ''; |
| | | |
| | | /** |
| | | * Directory containing the GPG key files |
| | | * |
| | | * This property only contains the path when the <i>homedir</i> option |
| | |
| | | private $_pipes = array(); |
| | | |
| | | /** |
| | | * Array of pipes used for communication with the gpg-agent binary |
| | | * |
| | | * This is an array of file descriptor resources. |
| | | * |
| | | * @var array |
| | | */ |
| | | private $_agentPipes = array(); |
| | | |
| | | /** |
| | | * Array of currently opened pipes |
| | | * |
| | | * This array is used to keep track of remaining opened pipes so they can |
| | |
| | | * @var resource |
| | | */ |
| | | private $_process = null; |
| | | |
| | | /** |
| | | * A handle for the gpg-agent process |
| | | * |
| | | * @var resource |
| | | */ |
| | | private $_agentProcess = null; |
| | | |
| | | /** |
| | | * GPG agent daemon socket and PID for running gpg-agent |
| | | * |
| | | * @var string |
| | | */ |
| | | private $_agentInfo = null; |
| | | |
| | | /** |
| | | * Whether or not the operating system is Darwin (OS X) |
| | |
| | | */ |
| | | 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() |
| | | |
| | |
| | | * operating system. The option |
| | | * <kbd>gpgBinary</kbd> is a |
| | | * deprecated alias for this option. |
| | | * - <kbd>string agent</kbd> - the location of the GnuPG agent |
| | | * binary. The gpg-agent is only |
| | | * used for GnuPG 2.x. If not |
| | | * specified, the engine attempts |
| | | * to auto-detect the gpg-agent |
| | | * binary location using a list of |
| | | * know default locations for the |
| | | * current operating system. |
| | | * - <kbd>boolean debug</kbd> - whether or not to use debug mode. |
| | | * When debug mode is on, all |
| | | * communication to and from the GPG |
| | |
| | | * @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. |
| | | * |
| | | * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or |
| | | * if no <kbd>agent</kbd> is provided and no suitable gpg-agent |
| | | * cound 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'; |
| | | if (extension_loaded('posix')) { |
| | | // note: this requires the package OS dep exclude 'windows' |
| | | $info = posix_getpwuid(posix_getuid()); |
| | | $this->_homedir = $info['dir'].'/.gnupg'; |
| | | } else { |
| | | if (isset($_SERVER['HOME'])) { |
| | | $this->_homedir = $_SERVER['HOME']; |
| | | } else { |
| | | $this->_homedir = getenv('HOME'); |
| | | } |
| | | } |
| | | |
| | | if ($this->_homedir === false) { |
| | | throw new Crypt_GPG_FileException( |
| | | 'Could not locate homedir. Please specify the homedir ' . |
| | | 'to use with the \'homedir\' option when instantiating ' . |
| | | 'the Crypt_GPG object.' |
| | | ); |
| | | } |
| | | } |
| | | |
| | | // attempt to create homedir if it does not exist |
| | |
| | | // 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); |
| | | 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 |
| | | ); |
| | | } |
| | | } |
| | | |
| | | // check homedir permissions (See Bug #19833) |
| | | if (!is_executable($this->_homedir)) { |
| | | throw new Crypt_GPG_FileException( |
| | | 'The \'homedir\' "' . $this->_homedir . '" is not enterable ' . |
| | | 'by the current user. Please check the permissions on your ' . |
| | | 'homedir and make sure the current user can both enter and ' . |
| | | 'write to the directory.', |
| | | 0, |
| | | $this->_homedir |
| | | ); |
| | | } |
| | | if (!is_writeable($this->_homedir)) { |
| | | throw new Crypt_GPG_FileException( |
| | | 'The \'homedir\' "' . $this->_homedir . '" is not writable ' . |
| | | 'by the current user. Please check the permissions on your ' . |
| | | 'homedir and make sure the current user can both enter and ' . |
| | | 'write to the directory.', |
| | | 0, |
| | | $this->_homedir |
| | | ); |
| | | } |
| | | |
| | | // get binary |
| | |
| | | } |
| | | |
| | | 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.'); |
| | | 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.' |
| | | ); |
| | | } |
| | | |
| | | // get agent |
| | | if (array_key_exists('agent', $options)) { |
| | | $this->_agent = (string)$options['agent']; |
| | | } else { |
| | | $this->_agent = $this->_getAgent(); |
| | | } |
| | | |
| | | if ($this->_agent == '' || !is_executable($this->_agent)) { |
| | | throw new PEAR_Exception( |
| | | 'gpg-agent binary not found. If you are sure the gpg-agent ' . |
| | | 'is installed, please specify the location of the gpg-agent ' . |
| | | 'binary using the \'agent\' driver option.' |
| | | ); |
| | | } |
| | | |
| | | /* |
| | |
| | | } |
| | | |
| | | $matches = array(); |
| | | $expression = '/gpg \(GnuPG\) (\S+)/'; |
| | | $expression = '#gpg \(GnuPG[A-Za-z0-9/]*?\) (\S+)#'; |
| | | |
| | | if (preg_match($expression, $info, $matches) === 1) { |
| | | $this->_version = $matches[1]; |
| | |
| | | $fdCommand = $this->_pipes[self::FD_COMMAND]; |
| | | $fdMessage = $this->_pipes[self::FD_MESSAGE]; |
| | | |
| | | // select loop delay in milliseconds |
| | | $delay = 0; |
| | | |
| | | while (true) { |
| | | |
| | | $inputStreams = array(); |
| | |
| | | $outputStreams[] = $this->_output; |
| | | } |
| | | |
| | | if ($this->_commandBuffer != '') { |
| | | if ($this->_commandBuffer != '' && is_resource($fdCommand)) { |
| | | $outputStreams[] = $fdCommand; |
| | | } |
| | | |
| | | if ($messageBuffer != '') { |
| | | if ($messageBuffer != '' && is_resource($fdMessage)) { |
| | | $outputStreams[] = $fdMessage; |
| | | } |
| | | |
| | | if ($inputBuffer != '') { |
| | | if ($inputBuffer != '' && is_resource($fdInput)) { |
| | | $outputStreams[] = $fdInput; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // write input (to GPG) |
| | | if (in_array($fdInput, $outputStreams)) { |
| | | if (in_array($fdInput, $outputStreams, true)) { |
| | | $this->_debug('GPG is ready for input'); |
| | | |
| | | $chunk = self::_byteSubstring( |
| | | $chunk = Crypt_GPG_ByteUtils::substr( |
| | | $inputBuffer, |
| | | 0, |
| | | self::CHUNK_SIZE |
| | | ); |
| | | |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($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 |
| | | ); |
| | | if ($length === 0) { |
| | | // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since |
| | | // the pipe was seleted for writing, we assume it was EPIPE. |
| | | // There's no way to get the actual erorr code in PHP. See |
| | | // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598 |
| | | $this->_debug('=> broken pipe on GPG input'); |
| | | $this->_debug('=> closing pipe GPG input'); |
| | | $this->_closePipe(self::FD_INPUT); |
| | | } else { |
| | | $this->_debug('=> wrote ' . $length . ' bytes'); |
| | | $inputBuffer = Crypt_GPG_ByteUtils::substr( |
| | | $inputBuffer, |
| | | $length |
| | | ); |
| | | } |
| | | } |
| | | |
| | | // read input (from PHP stream) |
| | | if (in_array($this->_input, $inputStreams)) { |
| | | if (in_array($this->_input, $inputStreams, true)) { |
| | | $this->_debug('input stream is ready for reading'); |
| | | $this->_debug( |
| | | '=> about to read ' . self::CHUNK_SIZE . |
| | |
| | | ); |
| | | |
| | | $chunk = fread($this->_input, self::CHUNK_SIZE); |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($chunk); |
| | | $inputBuffer .= $chunk; |
| | | |
| | | $this->_debug('=> read ' . $length . ' bytes'); |
| | | } |
| | | |
| | | // write message (to GPG) |
| | | if (in_array($fdMessage, $outputStreams)) { |
| | | if (in_array($fdMessage, $outputStreams, true)) { |
| | | $this->_debug('GPG is ready for message data'); |
| | | |
| | | $chunk = self::_byteSubstring( |
| | | $chunk = Crypt_GPG_ByteUtils::substr( |
| | | $messageBuffer, |
| | | 0, |
| | | self::CHUNK_SIZE |
| | | ); |
| | | |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($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); |
| | | if ($length === 0) { |
| | | // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since |
| | | // the pipe was seleted for writing, we assume it was EPIPE. |
| | | // There's no way to get the actual erorr code in PHP. See |
| | | // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598 |
| | | $this->_debug('=> broken pipe on GPG message'); |
| | | $this->_debug('=> closing pipe GPG message'); |
| | | $this->_closePipe(self::FD_MESSAGE); |
| | | } else { |
| | | $this->_debug('=> wrote ' . $length . ' bytes'); |
| | | $messageBuffer = Crypt_GPG_ByteUtils::substr( |
| | | $messageBuffer, |
| | | $length |
| | | ); |
| | | } |
| | | } |
| | | |
| | | // read message (from PHP stream) |
| | | if (in_array($this->_message, $inputStreams)) { |
| | | if (in_array($this->_message, $inputStreams, true)) { |
| | | $this->_debug('message stream is ready for reading'); |
| | | $this->_debug( |
| | | '=> about to read ' . self::CHUNK_SIZE . |
| | |
| | | ); |
| | | |
| | | $chunk = fread($this->_message, self::CHUNK_SIZE); |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($chunk); |
| | | $messageBuffer .= $chunk; |
| | | |
| | | $this->_debug('=> read ' . $length . ' bytes'); |
| | | } |
| | | |
| | | // read output (from GPG) |
| | | if (in_array($fdOutput, $inputStreams)) { |
| | | if (in_array($fdOutput, $inputStreams, true)) { |
| | | $this->_debug('GPG output stream ready for reading'); |
| | | $this->_debug( |
| | | '=> about to read ' . self::CHUNK_SIZE . |
| | |
| | | ); |
| | | |
| | | $chunk = fread($fdOutput, self::CHUNK_SIZE); |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($chunk); |
| | | $outputBuffer .= $chunk; |
| | | |
| | | $this->_debug('=> read ' . $length . ' bytes'); |
| | | } |
| | | |
| | | // write output (to PHP stream) |
| | | if (in_array($this->_output, $outputStreams)) { |
| | | if (in_array($this->_output, $outputStreams, true)) { |
| | | $this->_debug('output stream is ready for data'); |
| | | |
| | | $chunk = self::_byteSubstring( |
| | | $chunk = Crypt_GPG_ByteUtils::substr( |
| | | $outputBuffer, |
| | | 0, |
| | | self::CHUNK_SIZE |
| | | ); |
| | | |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($chunk); |
| | | |
| | | $this->_debug( |
| | | '=> about to write ' . $length . ' bytes to output stream' |
| | |
| | | |
| | | $this->_debug('=> wrote ' . $length . ' bytes'); |
| | | |
| | | $outputBuffer = self::_byteSubstring($outputBuffer, $length); |
| | | $outputBuffer = Crypt_GPG_ByteUtils::substr( |
| | | $outputBuffer, |
| | | $length |
| | | ); |
| | | } |
| | | |
| | | // read error (from GPG) |
| | | if (in_array($fdError, $inputStreams)) { |
| | | if (in_array($fdError, $inputStreams, true)) { |
| | | $this->_debug('GPG error stream ready for reading'); |
| | | $this->_debug( |
| | | '=> about to read ' . self::CHUNK_SIZE . |
| | |
| | | ); |
| | | |
| | | $chunk = fread($fdError, self::CHUNK_SIZE); |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($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); |
| | | $line = Crypt_GPG_ByteUtils::substr($errorBuffer, 0, $pos); |
| | | foreach ($this->_errorHandlers as $handler) { |
| | | array_unshift($handler['args'], $line); |
| | | call_user_func_array( |
| | |
| | | |
| | | array_shift($handler['args']); |
| | | } |
| | | $errorBuffer = self::_byteSubString( |
| | | $errorBuffer = Crypt_GPG_ByteUtils::substr( |
| | | $errorBuffer, |
| | | $pos + self::_byteLength(PHP_EOL) |
| | | $pos + Crypt_GPG_ByteUtils::strlen(PHP_EOL) |
| | | ); |
| | | } |
| | | } |
| | | |
| | | // read status (from GPG) |
| | | if (in_array($fdStatus, $inputStreams)) { |
| | | if (in_array($fdStatus, $inputStreams, true)) { |
| | | $this->_debug('GPG status stream ready for reading'); |
| | | $this->_debug( |
| | | '=> about to read ' . self::CHUNK_SIZE . |
| | |
| | | ); |
| | | |
| | | $chunk = fread($fdStatus, self::CHUNK_SIZE); |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($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); |
| | | $line = Crypt_GPG_ByteUtils::substr($statusBuffer, 0, $pos); |
| | | // only pass lines beginning with magic prefix |
| | | if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') { |
| | | $line = self::_byteSubstring($line, 9); |
| | | if (Crypt_GPG_ByteUtils::substr($line, 0, 9) == '[GNUPG:] ') { |
| | | $line = Crypt_GPG_ByteUtils::substr($line, 9); |
| | | foreach ($this->_statusHandlers as $handler) { |
| | | array_unshift($handler['args'], $line); |
| | | call_user_func_array( |
| | |
| | | array_shift($handler['args']); |
| | | } |
| | | } |
| | | $statusBuffer = self::_byteSubString( |
| | | $statusBuffer = Crypt_GPG_ByteUtils::substr( |
| | | $statusBuffer, |
| | | $pos + self::_byteLength(PHP_EOL) |
| | | $pos + Crypt_GPG_ByteUtils::strlen(PHP_EOL) |
| | | ); |
| | | } |
| | | } |
| | | |
| | | // write command (to GPG) |
| | | if (in_array($fdCommand, $outputStreams)) { |
| | | if (in_array($fdCommand, $outputStreams, true)) { |
| | | $this->_debug('GPG is ready for command data'); |
| | | |
| | | // send commands |
| | | $chunk = self::_byteSubstring( |
| | | $chunk = Crypt_GPG_ByteUtils::substr( |
| | | $this->_commandBuffer, |
| | | 0, |
| | | self::CHUNK_SIZE |
| | | ); |
| | | |
| | | $length = self::_byteLength($chunk); |
| | | $length = Crypt_GPG_ByteUtils::strlen($chunk); |
| | | |
| | | $this->_debug( |
| | | '=> about to write ' . $length . ' bytes to GPG command' |
| | | ); |
| | | |
| | | $length = fwrite($fdCommand, $chunk, $length); |
| | | if ($length === 0) { |
| | | // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since |
| | | // the pipe was seleted for writing, we assume it was EPIPE. |
| | | // There's no way to get the actual erorr code in PHP. See |
| | | // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598 |
| | | $this->_debug('=> broken pipe on GPG command'); |
| | | $this->_debug('=> closing pipe GPG command'); |
| | | $this->_closePipe(self::FD_COMMAND); |
| | | } else { |
| | | $this->_debug('=> wrote ' . $length); |
| | | $this->_commandBuffer = Crypt_GPG_ByteUtils::substr( |
| | | $this->_commandBuffer, |
| | | $length |
| | | ); |
| | | } |
| | | } |
| | | |
| | | $this->_debug('=> wrote ' . $length); |
| | | if (count($outputStreams) === 0 || count($inputStreams) === 0) { |
| | | // we have an I/O imbalance, increase the select loop delay |
| | | // to smooth things out |
| | | $delay += 10; |
| | | } else { |
| | | // things are running smoothly, decrease the delay |
| | | $delay -= 8; |
| | | $delay = max(0, $delay); |
| | | } |
| | | |
| | | $this->_commandBuffer = self::_byteSubstring( |
| | | $this->_commandBuffer, |
| | | $length |
| | | ); |
| | | if ($delay > 0) { |
| | | usleep($delay); |
| | | } |
| | | |
| | | } // end loop while streams are open |
| | |
| | | { |
| | | $version = $this->getVersion(); |
| | | |
| | | // 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'; |
| | | |
| | | $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'; |
| | | |
| | | // If using GnuPG 2.x start the gpg-agent |
| | | if (version_compare($version, '2.0.0', 'ge')) { |
| | | $agentCommandLine = $this->_agent; |
| | | |
| | | $agentArguments = array( |
| | | '--options /dev/null', // ignore any saved options |
| | | '--csh', // output is easier to parse |
| | | '--keep-display', // prevent passing --display to pinentry |
| | | '--no-grab', |
| | | '--ignore-cache-for-signing', |
| | | '--pinentry-touch-file /dev/null', |
| | | '--disable-scdaemon', |
| | | '--no-use-standard-socket', |
| | | '--pinentry-program ' . escapeshellarg($this->_getPinEntry()) |
| | | ); |
| | | |
| | | if ($this->_homedir) { |
| | | $agentArguments[] = '--homedir ' . |
| | | escapeshellarg($this->_homedir); |
| | | } |
| | | |
| | | |
| | | $agentCommandLine .= ' ' . implode(' ', $agentArguments) |
| | | . ' --daemon'; |
| | | |
| | | $agentDescriptorSpec = array( |
| | | self::FD_INPUT => array('pipe', $rb), // stdin |
| | | self::FD_OUTPUT => array('pipe', $wb), // stdout |
| | | self::FD_ERROR => array('pipe', $wb) // stderr |
| | | ); |
| | | |
| | | $this->_debug('OPENING GPG-AGENT SUBPROCESS WITH THE FOLLOWING COMMAND:'); |
| | | $this->_debug($agentCommandLine); |
| | | |
| | | $this->_agentProcess = proc_open( |
| | | $agentCommandLine, |
| | | $agentDescriptorSpec, |
| | | $this->_agentPipes, |
| | | null, |
| | | $env, |
| | | array('binary_pipes' => true) |
| | | ); |
| | | |
| | | if (!is_resource($this->_agentProcess)) { |
| | | throw new Crypt_GPG_OpenSubprocessException( |
| | | 'Unable to open gpg-agent subprocess.', |
| | | 0, |
| | | $agentCommandLine |
| | | ); |
| | | } |
| | | |
| | | // Get GPG_AGENT_INFO and set environment variable for gpg process. |
| | | // This is a blocking read, but is only 1 line. |
| | | $agentInfo = fread( |
| | | $this->_agentPipes[self::FD_OUTPUT], |
| | | self::CHUNK_SIZE |
| | | ); |
| | | |
| | | $agentInfo = explode(' ', $agentInfo, 3); |
| | | $this->_agentInfo = $agentInfo[2]; |
| | | $env['GPG_AGENT_INFO'] = $this->_agentInfo; |
| | | |
| | | // gpg-agent daemon is started, we can close the launching process |
| | | $this->_closeAgentLaunchProcess(); |
| | | } |
| | | |
| | | $commandLine = $this->_binary; |
| | | |
| | |
| | | $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_MESSAGE => array('pipe', $rb) // message |
| | | ); |
| | | |
| | | $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:'); |
| | | $this->_debug('OPENING GPG SUBPROCESS WITH THE FOLLOWING COMMAND:'); |
| | | $this->_debug($commandLine); |
| | | |
| | | $this->_process = proc_open( |
| | |
| | | if (!is_resource($this->_process)) { |
| | | throw new Crypt_GPG_OpenSubprocessException( |
| | | 'Unable to open GPG subprocess.', 0, $commandLine); |
| | | } |
| | | |
| | | // Set streams as non-blocking. See Bug #18618. |
| | | foreach ($this->_pipes as $pipe) { |
| | | stream_set_blocking($pipe, 0); |
| | | } |
| | | |
| | | $this->_openPipes = $this->_pipes; |
| | |
| | | */ |
| | | private function _closeSubprocess() |
| | | { |
| | | // clear PINs from environment if they were set |
| | | $_ENV['PINENTRY_USER_DATA'] = null; |
| | | |
| | | if (is_resource($this->_process)) { |
| | | $this->_debug('CLOSING SUBPROCESS'); |
| | | $this->_debug('CLOSING GPG SUBPROCESS'); |
| | | |
| | | // close remaining open pipes |
| | | foreach (array_keys($this->_openPipes) as $pipeNumber) { |
| | |
| | | $this->_process = null; |
| | | $this->_pipes = array(); |
| | | } |
| | | |
| | | $this->_closeAgentLaunchProcess(); |
| | | |
| | | if ($this->_agentInfo !== null) { |
| | | $this->_debug('STOPPING GPG-AGENT DAEMON'); |
| | | |
| | | $parts = explode(':', $this->_agentInfo, 3); |
| | | $pid = $parts[1]; |
| | | $process = new Crypt_GPG_ProcessControl($pid); |
| | | |
| | | // terminate agent daemon |
| | | $process->terminate(); |
| | | |
| | | while ($process->isRunning()) { |
| | | usleep(10000); // 10 ms |
| | | $process->terminate(); |
| | | } |
| | | |
| | | $this->_agentInfo = null; |
| | | |
| | | $this->_debug('GPG-AGENT DAEMON STOPPED'); |
| | | } |
| | | } |
| | | |
| | | // }}} |
| | | // {{ _closeAgentLaunchProcess() |
| | | |
| | | private function _closeAgentLaunchProcess() |
| | | { |
| | | if (is_resource($this->_agentProcess)) { |
| | | $this->_debug('CLOSING GPG-AGENT LAUNCH PROCESS'); |
| | | |
| | | // close agent pipes |
| | | foreach ($this->_agentPipes as $pipe) { |
| | | fflush($pipe); |
| | | fclose($pipe); |
| | | } |
| | | |
| | | // close agent launching process |
| | | proc_close($this->_agentProcess); |
| | | |
| | | $this->_agentProcess = null; |
| | | $this->_agentPipes = array(); |
| | | |
| | | $this->_debug('GPG-AGENT LAUNCH PROCESS CLOSED'); |
| | | } |
| | | } |
| | | |
| | | // }} |
| | | // {{{ _closePipe() |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | // }}} |
| | | // {{ _getAgent() |
| | | |
| | | private function _getAgent() |
| | | { |
| | | $agent = ''; |
| | | |
| | | if ($this->_isDarwin) { |
| | | $agentFiles = array( |
| | | '/opt/local/bin/gpg-agent', // MacPorts |
| | | '/usr/local/bin/gpg-agent', // Mac GPG |
| | | '/sw/bin/gpg-agent', // Fink |
| | | '/usr/bin/gpg-agent' |
| | | ); |
| | | } else { |
| | | $agentFiles = array( |
| | | '/usr/bin/gpg-agent', |
| | | '/usr/local/bin/gpg-agent' |
| | | ); |
| | | } |
| | | |
| | | foreach ($agentFiles as $agentFile) { |
| | | if (is_executable($agentFile)) { |
| | | $agent = $agentFile; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | return $agent; |
| | | } |
| | | |
| | | // }} |
| | | // {{ _getPinEntry() |
| | | |
| | | private function _getPinEntry() |
| | | { |
| | | // Check if we're running directly from git or if we're using a |
| | | // PEAR-packaged version |
| | | $pinEntry = '@bin-dir@' . DIRECTORY_SEPARATOR . 'crypt-gpg-pinentry'; |
| | | |
| | | if ($pinEntry[0] === '@') { |
| | | $pinEntry = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' |
| | | . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'scripts' |
| | | . DIRECTORY_SEPARATOR . 'crypt-gpg-pinentry'; |
| | | } |
| | | |
| | | return $pinEntry; |
| | | } |
| | | |
| | | // }} |
| | | // {{{ _debug() |
| | | |
| | | /** |
| | |
| | | private function _debug($text) |
| | | { |
| | | if ($this->_debug) { |
| | | if (array_key_exists('SHELL', $_ENV)) { |
| | | if (php_sapi_name() === 'cli') { |
| | | foreach (explode(PHP_EOL, $text) as $line) { |
| | | echo "Crypt_GPG DEBUG: ", $line, 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); |
| | | } |
| | | |
| | | // }}} |