Crypt/GPG/Engine.php
private function _openSubprocess()
    {
        $version = $this->getVersion();

        // log versions, but not when looking for the version number
        if ($version !== '1.0.0') {
            $this->_debug('USING GPG ' . $version . ' with PHP ' . PHP_VERSION);
        }

        // 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 < 2.1.13 start the gpg-agent
        if (version_compare($version, '2.0.0', 'ge')
            && version_compare($version, '2.1.13', 'lt')
        ) {
            if (!$this->_agent) {
                throw new Crypt_GPG_OpenSubprocessException(
                    'Unable to open gpg-agent subprocess (gpg-agent not found). ' .
                    'Please specify location of the gpg-agent binary ' .
                    'using the \'agent\' driver option.'
                );
            }

            $agentArguments = array(
                '--daemon',
                '--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);
            }

            if ($version21 = version_compare($version, '2.1.0', 'ge')) {
                // This is needed to get socket file location in stderr output
                // Note: This does not help when the agent already is running
                $agentArguments[] = '--verbose';
            }

            $agentCommandLine = $this->_agent . ' ' . implode(' ', $agentArguments);

            $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);

            // For GnuPG 2.1 we need to read both stderr and stdout
            if ($version21) {
                $agentInfo .= "\n" . fread($this->_agentPipes[self::FD_ERROR], self::CHUNK_SIZE);
            }

            if ($agentInfo) {
                foreach (explode("\n", $agentInfo) as $line) {
                    if ($version21) {
                        if (preg_match('/listening on socket \'([^\']+)/', $line, $m)) {
                            $this->_agentInfo = $m[1];
                        } else if (preg_match('/gpg-agent\[([0-9]+)\].* started/', $line, $m)) {
                            $this->_agentInfo .= ':' . $m[1] . ':1';
                        }
                    } else if (preg_match('/GPG_AGENT_INFO[=\s]([^;]+)/', $line, $m)) {
                        $this->_agentInfo = $m[1];
                        break;
                    }
                }
            }

            $this->_debug('GPG-AGENT-INFO: ' . $this->_agentInfo);

            $env['GPG_AGENT_INFO'] = $this->_agentInfo;

            // gpg-agent daemon is started, we can close the launching process
            $this->_closeAgentLaunchProcess();

            // Terminate processes if something went wrong
            register_shutdown_function(array($this, '__destruct'));
        }

        // "Register" GPGConf existence for _closeIdleAgents()
        if (version_compare($version, '2.1.0', 'ge')) {
            if ($this->_gpgconf === null) {
                $this->_gpgconf = $this->_getGPGConf();
            }
        } else {
            $this->_gpgconf = false;
        }

        $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';
        }

        // Since 2.1.13 we can use "loopback mode" instead of gpg-agent
        if (version_compare($version, '2.1.13', 'ge')) {
            $defaultArguments[] = '--pinentry-mode loopback';
        }

        if (!$this->_strict) {
            $defaultArguments[] = '--ignore-time-conflict';
            $defaultArguments[] = '--ignore-valid-from';
        }

        if (!empty($this->_digest_algo)) {
            $defaultArguments[] = '--digest-algo ' . escapeshellarg($this->_digest_algo);
            $defaultArguments[] = '--s2k-digest-algo ' . escapeshellarg($this->_digest_algo);
        }

        if (!empty($this->_cipher_algo)) {
            $defaultArguments[] = '--cipher-algo ' . escapeshellarg($this->_cipher_algo);
            $defaultArguments[] = '--s2k-cipher-algo ' . escapeshellarg($this->_cipher_algo);
        }

        $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;

        $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 GPG 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
            );
        }

        // Set streams as non-blocking. See Bug #18618.
        foreach ($this->_pipes as $pipe) {
            stream_set_blocking($pipe, 0);
            stream_set_write_buffer($pipe, self::CHUNK_SIZE);
            stream_set_chunk_size($pipe, self::CHUNK_SIZE);
            stream_set_read_buffer($pipe, self::CHUNK_SIZE);
        }

        $this->_openPipes = $this->_pipes;
    }
private function _openSubprocess()
    {
        $version = $this->getVersion();

        // log versions, but not when looking for the version number
        if ($version !== '1.0.0') {
            $this->_debug('USING GPG ' . $version . ' with PHP ' . PHP_VERSION);
        }

        // 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 < 2.1.13 start the gpg-agent
        if (version_compare($version, '2.0.0', 'ge')
            && version_compare($version, '2.1.13', 'lt')
        ) {
            if (!$this->_agent) {
                throw new Crypt_GPG_OpenSubprocessException(
                    'Unable to open gpg-agent subprocess (gpg-agent not found). ' .
                    'Please specify location of the gpg-agent binary ' .
                    'using the \'agent\' driver option.'
                );
            }

            $agentArguments = array(
                '--daemon',
                '--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);
            }

            if ($version21 = version_compare($version, '2.1.0', 'ge')) {
                // This is needed to get socket file location in stderr output
                // Note: This does not help when the agent already is running
                $agentArguments[] = '--verbose';
            }

            $agentCommandLine = $this->_agent . ' ' . implode(' ', $agentArguments);

            $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);

            // For GnuPG 2.1 we need to read both stderr and stdout
            if ($version21) {
                $agentInfo .= "\n" . fread($this->_agentPipes[self::FD_ERROR], self::CHUNK_SIZE);
            }

            if ($agentInfo) {
                foreach (explode("\n", $agentInfo) as $line) {
                    if ($version21) {
                        if (preg_match('/listening on socket \'([^\']+)/', $line, $m)) {
                            $this->_agentInfo = $m[1];
                        } else if (preg_match('/gpg-agent\[([0-9]+)\].* started/', $line, $m)) {
                            $this->_agentInfo .= ':' . $m[1] . ':1';
                        }
                    } else if (preg_match('/GPG_AGENT_INFO[=\s]([^;]+)/', $line, $m)) {
                        $this->_agentInfo = $m[1];
                        break;
                    }
                }
            }

            $this->_debug('GPG-AGENT-INFO: ' . $this->_agentInfo);

            $env['GPG_AGENT_INFO'] = $this->_agentInfo;

            // gpg-agent daemon is started, we can close the launching process
            $this->_closeAgentLaunchProcess();

            // Terminate processes if something went wrong
            register_shutdown_function(array($this, '__destruct'));
        }

        // "Register" GPGConf existence for _closeIdleAgents()
        if (version_compare($version, '2.1.0', 'ge')) {
            if ($this->_gpgconf === null) {
                $this->_gpgconf = $this->_getGPGConf();
            }
        } else {
            $this->_gpgconf = false;
        }

        $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';
        }

        // Since 2.1.13 we can use "loopback mode" instead of gpg-agent
        if (version_compare($version, '2.1.13', 'ge')) {
            $defaultArguments[] = '--pinentry-mode loopback';
        }

        if (!$this->_strict) {
            $defaultArguments[] = '--ignore-time-conflict';
            $defaultArguments[] = '--ignore-valid-from';
        }

        if (!empty($this->_digest_algo)) {
            $defaultArguments[] = '--digest-algo ' . escapeshellarg($this->_digest_algo);
            $defaultArguments[] = '--s2k-digest-algo ' . escapeshellarg($this->_digest_algo);
        }

        if (!empty($this->_cipher_algo)) {
            $defaultArguments[] = '--cipher-algo ' . escapeshellarg($this->_cipher_algo);
            $defaultArguments[] = '--s2k-cipher-algo ' . escapeshellarg($this->_cipher_algo);
        }

        $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;

        $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 GPG 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
            );
        }

        // Set streams as non-blocking. See Bug #18618.
        foreach ($this->_pipes as $pipe) {
            stream_set_blocking($pipe, 0);
            stream_set_write_buffer($pipe, self::CHUNK_SIZE);
            stream_set_chunk_size($pipe, self::CHUNK_SIZE);
            stream_set_read_buffer($pipe, self::CHUNK_SIZE);
        }

        $this->_openPipes = $this->_pipes;
    }
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