]> git.donarmstrong.com Git - roundcube.git/blob - plugins/enigma/lib/Crypt/GPG/Engine.php
Imported Upstream version 0.6+dfsg
[roundcube.git] / plugins / enigma / lib / Crypt / GPG / Engine.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * Crypt_GPG is a package to use GPG from PHP
7  *
8  * This file contains an engine that handles GPG subprocess control and I/O.
9  * PHP's process manipulation functions are used to handle the GPG subprocess.
10  *
11  * PHP version 5
12  *
13  * LICENSE:
14  *
15  * This library is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU Lesser General Public License as
17  * published by the Free Software Foundation; either version 2.1 of the
18  * License, or (at your option) any later version.
19  *
20  * This library is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23  * Lesser General Public License for more details.
24  *
25  * You should have received a copy of the GNU Lesser General Public
26  * License along with this library; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28  *
29  * @category  Encryption
30  * @package   Crypt_GPG
31  * @author    Nathan Fredrickson <nathan@silverorange.com>
32  * @author    Michael Gauthier <mike@silverorange.com>
33  * @copyright 2005-2010 silverorange
34  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
35  * @version   CVS: $Id: Engine.php 302822 2010-08-26 17:30:57Z gauthierm $
36  * @link      http://pear.php.net/package/Crypt_GPG
37  * @link      http://www.gnupg.org/
38  */
39
40 /**
41  * Crypt_GPG base class.
42  */
43 require_once 'Crypt/GPG.php';
44
45 /**
46  * GPG exception classes.
47  */
48 require_once 'Crypt/GPG/Exceptions.php';
49
50 /**
51  * Standard PEAR exception is used if GPG binary is not found.
52  */
53 require_once 'PEAR/Exception.php';
54
55 // {{{ class Crypt_GPG_Engine
56
57 /**
58  * Native PHP Crypt_GPG I/O engine
59  *
60  * This class is used internally by Crypt_GPG and does not need be used
61  * directly. See the {@link Crypt_GPG} class for end-user API.
62  *
63  * This engine uses PHP's native process control functions to directly control
64  * the GPG process. The GPG executable is required to be on the system.
65  *
66  * All data is passed to the GPG subprocess using file descriptors. This is the
67  * most secure method of passing data to the GPG subprocess.
68  *
69  * @category  Encryption
70  * @package   Crypt_GPG
71  * @author    Nathan Fredrickson <nathan@silverorange.com>
72  * @author    Michael Gauthier <mike@silverorange.com>
73  * @copyright 2005-2010 silverorange
74  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
75  * @link      http://pear.php.net/package/Crypt_GPG
76  * @link      http://www.gnupg.org/
77  */
78 class Crypt_GPG_Engine
79 {
80     // {{{ constants
81
82     /**
83      * Size of data chunks that are sent to and retrieved from the IPC pipes.
84      *
85      * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192
86      * and buffers the rest so we might as well just read 8192.
87      *
88      * Using values other than 8192 also triggers PHP bugs.
89      *
90      * @see http://bugs.php.net/bug.php?id=35224
91      */
92     const CHUNK_SIZE = 8192;
93
94     /**
95      * Standard input file descriptor. This is used to pass data to the GPG
96      * process.
97      */
98     const FD_INPUT = 0;
99
100     /**
101      * Standard output file descriptor. This is used to receive normal output
102      * from the GPG process.
103      */
104     const FD_OUTPUT = 1;
105
106     /**
107      * Standard output file descriptor. This is used to receive error output
108      * from the GPG process.
109      */
110     const FD_ERROR = 2;
111
112     /**
113      * GPG status output file descriptor. The status file descriptor outputs
114      * detailed information for many GPG commands. See the second section of
115      * the file <b>doc/DETAILS</b> in the
116      * {@link http://www.gnupg.org/download/ GPG package} for a detailed
117      * description of GPG's status output.
118      */
119     const FD_STATUS = 3;
120
121     /**
122      * Command input file descriptor. This is used for methods requiring
123      * passphrases.
124      */
125     const FD_COMMAND = 4;
126
127     /**
128      * Extra message input file descriptor. This is used for passing signed
129      * data when verifying a detached signature.
130      */
131     const FD_MESSAGE = 5;
132
133     /**
134      * Minimum version of GnuPG that is supported.
135      */
136     const MIN_VERSION = '1.0.2';
137
138     // }}}
139     // {{{ private class properties
140
141     /**
142      * Whether or not to use debugging mode
143      *
144      * When set to true, every GPG command is echoed before it is run. Sensitive
145      * data is always handled using pipes and is not specified as part of the
146      * command. As a result, sensitive data is never displayed when debug is
147      * enabled. Sensitive data includes private key data and passphrases.
148      *
149      * Debugging is off by default.
150      *
151      * @var boolean
152      * @see Crypt_GPG_Engine::__construct()
153      */
154     private $_debug = false;
155
156     /**
157      * Location of GPG binary
158      *
159      * @var string
160      * @see Crypt_GPG_Engine::__construct()
161      * @see Crypt_GPG_Engine::_getBinary()
162      */
163     private $_binary = '';
164
165     /**
166      * Directory containing the GPG key files
167      *
168      * This property only contains the path when the <i>homedir</i> option
169      * is specified in the constructor.
170      *
171      * @var string
172      * @see Crypt_GPG_Engine::__construct()
173      */
174     private $_homedir = '';
175
176     /**
177      * File path of the public keyring
178      *
179      * This property only contains the file path when the <i>public_keyring</i>
180      * option is specified in the constructor.
181      *
182      * If the specified file path starts with <kbd>~/</kbd>, the path is
183      * relative to the <i>homedir</i> if specified, otherwise to
184      * <kbd>~/.gnupg</kbd>.
185      *
186      * @var string
187      * @see Crypt_GPG_Engine::__construct()
188      */
189     private $_publicKeyring = '';
190
191     /**
192      * File path of the private (secret) keyring
193      *
194      * This property only contains the file path when the <i>private_keyring</i>
195      * option is specified in the constructor.
196      *
197      * If the specified file path starts with <kbd>~/</kbd>, the path is
198      * relative to the <i>homedir</i> if specified, otherwise to
199      * <kbd>~/.gnupg</kbd>.
200      *
201      * @var string
202      * @see Crypt_GPG_Engine::__construct()
203      */
204     private $_privateKeyring = '';
205
206     /**
207      * File path of the trust database
208      *
209      * This property only contains the file path when the <i>trust_db</i>
210      * option is specified in the constructor.
211      *
212      * If the specified file path starts with <kbd>~/</kbd>, the path is
213      * relative to the <i>homedir</i> if specified, otherwise to
214      * <kbd>~/.gnupg</kbd>.
215      *
216      * @var string
217      * @see Crypt_GPG_Engine::__construct()
218      */
219     private $_trustDb = '';
220
221     /**
222      * Array of pipes used for communication with the GPG binary
223      *
224      * This is an array of file descriptor resources.
225      *
226      * @var array
227      */
228     private $_pipes = array();
229
230     /**
231      * Array of currently opened pipes
232      *
233      * This array is used to keep track of remaining opened pipes so they can
234      * be closed when the GPG subprocess is finished. This array is a subset of
235      * the {@link Crypt_GPG_Engine::$_pipes} array and contains opened file
236      * descriptor resources.
237      *
238      * @var array
239      * @see Crypt_GPG_Engine::_closePipe()
240      */
241     private $_openPipes = array();
242
243     /**
244      * A handle for the GPG process
245      *
246      * @var resource
247      */
248     private $_process = null;
249
250     /**
251      * Whether or not the operating system is Darwin (OS X)
252      *
253      * @var boolean
254      */
255     private $_isDarwin = false;
256
257     /**
258      * Commands to be sent to GPG's command input stream
259      *
260      * @var string
261      * @see Crypt_GPG_Engine::sendCommand()
262      */
263     private $_commandBuffer = '';
264
265     /**
266      * Array of status line handlers
267      *
268      * @var array
269      * @see Crypt_GPG_Engine::addStatusHandler()
270      */
271     private $_statusHandlers = array();
272
273     /**
274      * Array of error line handlers
275      *
276      * @var array
277      * @see Crypt_GPG_Engine::addErrorHandler()
278      */
279     private $_errorHandlers = array();
280
281     /**
282      * The error code of the current operation
283      *
284      * @var integer
285      * @see Crypt_GPG_Engine::getErrorCode()
286      */
287     private $_errorCode = Crypt_GPG::ERROR_NONE;
288
289     /**
290      * File related to the error code of the current operation
291      *
292      * @var string
293      * @see Crypt_GPG_Engine::getErrorFilename()
294      */
295     private $_errorFilename = '';
296
297     /**
298      * Key id related to the error code of the current operation
299      *
300      * @var string
301      * @see Crypt_GPG_Engine::getErrorKeyId()
302      */
303     private $_errorkeyId = '';
304
305     /**
306      * The number of currently needed passphrases
307      *
308      * If this is not zero when the GPG command is completed, the error code is
309      * set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
310      *
311      * @var integer
312      */
313     private $_needPassphrase = 0;
314
315     /**
316      * The input source
317      *
318      * This is data to send to GPG. Either a string or a stream resource.
319      *
320      * @var string|resource
321      * @see Crypt_GPG_Engine::setInput()
322      */
323     private $_input = null;
324
325     /**
326      * The extra message input source
327      *
328      * Either a string or a stream resource.
329      *
330      * @var string|resource
331      * @see Crypt_GPG_Engine::setMessage()
332      */
333     private $_message = null;
334
335     /**
336      * The output location
337      *
338      * This is where the output from GPG is sent. Either a string or a stream
339      * resource.
340      *
341      * @var string|resource
342      * @see Crypt_GPG_Engine::setOutput()
343      */
344     private $_output = '';
345
346     /**
347      * The GPG operation to execute
348      *
349      * @var string
350      * @see Crypt_GPG_Engine::setOperation()
351      */
352     private $_operation;
353
354     /**
355      * Arguments for the current operation
356      *
357      * @var array
358      * @see Crypt_GPG_Engine::setOperation()
359      */
360     private $_arguments = array();
361
362     /**
363      * The version number of the GPG binary
364      *
365      * @var string
366      * @see Crypt_GPG_Engine::getVersion()
367      */
368     private $_version = '';
369
370     /**
371      * Cached value indicating whether or not mbstring function overloading is
372      * on for strlen
373      *
374      * This is cached for optimal performance inside the I/O loop.
375      *
376      * @var boolean
377      * @see Crypt_GPG_Engine::_byteLength()
378      * @see Crypt_GPG_Engine::_byteSubstring()
379      */
380     private static $_mbStringOverload = null;
381
382     // }}}
383     // {{{ __construct()
384
385     /**
386      * Creates a new GPG engine
387      *
388      * Available options are:
389      *
390      * - <kbd>string  homedir</kbd>        - the directory where the GPG
391      *                                       keyring files are stored. If not
392      *                                       specified, Crypt_GPG uses the
393      *                                       default of <kbd>~/.gnupg</kbd>.
394      * - <kbd>string  publicKeyring</kbd>  - the file path of the public
395      *                                       keyring. Use this if the public
396      *                                       keyring is not in the homedir, or
397      *                                       if the keyring is in a directory
398      *                                       not writable by the process
399      *                                       invoking GPG (like Apache). Then
400      *                                       you can specify the path to the
401      *                                       keyring with this option
402      *                                       (/foo/bar/pubring.gpg), and specify
403      *                                       a writable directory (like /tmp)
404      *                                       using the <i>homedir</i> option.
405      * - <kbd>string  privateKeyring</kbd> - the file path of the private
406      *                                       keyring. Use this if the private
407      *                                       keyring is not in the homedir, or
408      *                                       if the keyring is in a directory
409      *                                       not writable by the process
410      *                                       invoking GPG (like Apache). Then
411      *                                       you can specify the path to the
412      *                                       keyring with this option
413      *                                       (/foo/bar/secring.gpg), and specify
414      *                                       a writable directory (like /tmp)
415      *                                       using the <i>homedir</i> option.
416      * - <kbd>string  trustDb</kbd>        - the file path of the web-of-trust
417      *                                       database. Use this if the trust
418      *                                       database is not in the homedir, or
419      *                                       if the database is in a directory
420      *                                       not writable by the process
421      *                                       invoking GPG (like Apache). Then
422      *                                       you can specify the path to the
423      *                                       trust database with this option
424      *                                       (/foo/bar/trustdb.gpg), and specify
425      *                                       a writable directory (like /tmp)
426      *                                       using the <i>homedir</i> option.
427      * - <kbd>string  binary</kbd>         - the location of the GPG binary. If
428      *                                       not specified, the driver attempts
429      *                                       to auto-detect the GPG binary
430      *                                       location using a list of known
431      *                                       default locations for the current
432      *                                       operating system. The option
433      *                                       <kbd>gpgBinary</kbd> is a
434      *                                       deprecated alias for this option.
435      * - <kbd>boolean debug</kbd>          - whether or not to use debug mode.
436      *                                       When debug mode is on, all
437      *                                       communication to and from the GPG
438      *                                       subprocess is logged. This can be
439      *                                       useful to diagnose errors when
440      *                                       using Crypt_GPG.
441      *
442      * @param array $options optional. An array of options used to create the
443      *                       GPG object. All options are optional and are
444      *                       represented as key-value pairs.
445      *
446      * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
447      *         and cannot be created. This can happen if <kbd>homedir</kbd> is
448      *         not specified, Crypt_GPG is run as the web user, and the web
449      *         user has no home directory. This exception is also thrown if any
450      *         of the options <kbd>publicKeyring</kbd>,
451      *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
452      *         specified but the files do not exist or are are not readable.
453      *         This can happen if the user running the Crypt_GPG process (for
454      *         example, the Apache user) does not have permission to read the
455      *         files.
456      *
457      * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
458      *         if no <kbd>binary</kbd> is provided and no suitable binary could
459      *         be found.
460      */
461     public function __construct(array $options = array())
462     {
463         $this->_isDarwin = (strncmp(strtoupper(PHP_OS), 'DARWIN', 6) === 0);
464
465         // populate mbstring overloading cache if not set
466         if (self::$_mbStringOverload === null) {
467             self::$_mbStringOverload = (extension_loaded('mbstring')
468                 && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
469         }
470
471         // get homedir
472         if (array_key_exists('homedir', $options)) {
473             $this->_homedir = (string)$options['homedir'];
474         } else {
475             // note: this requires the package OS dep exclude 'windows'
476             $info = posix_getpwuid(posix_getuid());
477             $this->_homedir = $info['dir'].'/.gnupg';
478         }
479
480         // attempt to create homedir if it does not exist
481         if (!is_dir($this->_homedir)) {
482             if (@mkdir($this->_homedir, 0777, true)) {
483                 // Set permissions on homedir. Parent directories are created
484                 // with 0777, homedir is set to 0700.
485                 chmod($this->_homedir, 0700);
486             } else {
487                 throw new Crypt_GPG_FileException('The \'homedir\' "' .
488                     $this->_homedir . '" is not readable or does not exist '.
489                     'and cannot be created. This can happen if \'homedir\' '.
490                     'is not specified in the Crypt_GPG options, Crypt_GPG is '.
491                     'run as the web user, and the web user has no home '.
492                     'directory.',
493                     0, $this->_homedir);
494             }
495         }
496
497         // get binary
498         if (array_key_exists('binary', $options)) {
499             $this->_binary = (string)$options['binary'];
500         } elseif (array_key_exists('gpgBinary', $options)) {
501             // deprecated alias
502             $this->_binary = (string)$options['gpgBinary'];
503         } else {
504             $this->_binary = $this->_getBinary();
505         }
506
507         if ($this->_binary == '' || !is_executable($this->_binary)) {
508             throw new PEAR_Exception('GPG binary not found. If you are sure '.
509                 'the GPG binary is installed, please specify the location of '.
510                 'the GPG binary using the \'binary\' driver option.');
511         }
512
513         /*
514          * Note:
515          *
516          * Normally, GnuPG expects keyrings to be in the homedir and expects
517          * to be able to write temporary files in the homedir. Sometimes,
518          * keyrings are not in the homedir, or location of the keyrings does
519          * not allow writing temporary files. In this case, the <i>homedir</i>
520          * option by itself is not enough to specify the keyrings because GnuPG
521          * can not write required temporary files. Additional options are
522          * provided so you can specify the location of the keyrings separately
523          * from the homedir.
524          */
525
526         // get public keyring
527         if (array_key_exists('publicKeyring', $options)) {
528             $this->_publicKeyring = (string)$options['publicKeyring'];
529             if (!is_readable($this->_publicKeyring)) {
530                  throw new Crypt_GPG_FileException('The \'publicKeyring\' "' .
531                     $this->_publicKeyring . '" does not exist or is ' .
532                     'not readable. Check the location and ensure the file ' .
533                     'permissions are correct.', 0, $this->_publicKeyring);
534             }
535         }
536
537         // get private keyring
538         if (array_key_exists('privateKeyring', $options)) {
539             $this->_privateKeyring = (string)$options['privateKeyring'];
540             if (!is_readable($this->_privateKeyring)) {
541                  throw new Crypt_GPG_FileException('The \'privateKeyring\' "' .
542                     $this->_privateKeyring . '" does not exist or is ' .
543                     'not readable. Check the location and ensure the file ' .
544                     'permissions are correct.', 0, $this->_privateKeyring);
545             }
546         }
547
548         // get trust database
549         if (array_key_exists('trustDb', $options)) {
550             $this->_trustDb = (string)$options['trustDb'];
551             if (!is_readable($this->_trustDb)) {
552                  throw new Crypt_GPG_FileException('The \'trustDb\' "' .
553                     $this->_trustDb . '" does not exist or is not readable. ' .
554                     'Check the location and ensure the file permissions are ' .
555                     'correct.', 0, $this->_trustDb);
556             }
557         }
558
559         if (array_key_exists('debug', $options)) {
560             $this->_debug = (boolean)$options['debug'];
561         }
562     }
563
564     // }}}
565     // {{{ __destruct()
566
567     /**
568      * Closes open GPG subprocesses when this object is destroyed
569      *
570      * Subprocesses should never be left open by this class unless there is
571      * an unknown error and unexpected script termination occurs.
572      */
573     public function __destruct()
574     {
575         $this->_closeSubprocess();
576     }
577
578     // }}}
579     // {{{ addErrorHandler()
580
581     /**
582      * Adds an error handler method
583      *
584      * The method is run every time a new error line is received from the GPG
585      * subprocess. The handler method must accept the error line to be handled
586      * as its first parameter.
587      *
588      * @param callback $callback the callback method to use.
589      * @param array    $args     optional. Additional arguments to pass as
590      *                           parameters to the callback method.
591      *
592      * @return void
593      */
594     public function addErrorHandler($callback, array $args = array())
595     {
596         $this->_errorHandlers[] = array(
597             'callback' => $callback,
598             'args'     => $args
599         );
600     }
601
602     // }}}
603     // {{{ addStatusHandler()
604
605     /**
606      * Adds a status handler method
607      *
608      * The method is run every time a new status line is received from the
609      * GPG subprocess. The handler method must accept the status line to be
610      * handled as its first parameter.
611      *
612      * @param callback $callback the callback method to use.
613      * @param array    $args     optional. Additional arguments to pass as
614      *                           parameters to the callback method.
615      *
616      * @return void
617      */
618     public function addStatusHandler($callback, array $args = array())
619     {
620         $this->_statusHandlers[] = array(
621             'callback' => $callback,
622             'args'     => $args
623         );
624     }
625
626     // }}}
627     // {{{ sendCommand()
628
629     /**
630      * Sends a command to the GPG subprocess over the command file-descriptor
631      * pipe
632      *
633      * @param string $command the command to send.
634      *
635      * @return void
636      *
637      * @sensitive $command
638      */
639     public function sendCommand($command)
640     {
641         if (array_key_exists(self::FD_COMMAND, $this->_openPipes)) {
642             $this->_commandBuffer .= $command . PHP_EOL;
643         }
644     }
645
646     // }}}
647     // {{{ reset()
648
649     /**
650      * Resets the GPG engine, preparing it for a new operation
651      *
652      * @return void
653      *
654      * @see Crypt_GPG_Engine::run()
655      * @see Crypt_GPG_Engine::setOperation()
656      */
657     public function reset()
658     {
659         $this->_operation      = '';
660         $this->_arguments      = array();
661         $this->_input          = null;
662         $this->_message        = null;
663         $this->_output         = '';
664         $this->_errorCode      = Crypt_GPG::ERROR_NONE;
665         $this->_needPassphrase = 0;
666         $this->_commandBuffer  = '';
667
668         $this->_statusHandlers = array();
669         $this->_errorHandlers  = array();
670
671         $this->addStatusHandler(array($this, '_handleErrorStatus'));
672         $this->addErrorHandler(array($this, '_handleErrorError'));
673
674         if ($this->_debug) {
675             $this->addStatusHandler(array($this, '_handleDebugStatus'));
676             $this->addErrorHandler(array($this, '_handleDebugError'));
677         }
678     }
679
680     // }}}
681     // {{{ run()
682
683     /**
684      * Runs the current GPG operation
685      *
686      * This creates and manages the GPG subprocess.
687      *
688      * The operation must be set with {@link Crypt_GPG_Engine::setOperation()}
689      * before this method is called.
690      *
691      * @return void
692      *
693      * @throws Crypt_GPG_InvalidOperationException if no operation is specified.
694      *
695      * @see Crypt_GPG_Engine::reset()
696      * @see Crypt_GPG_Engine::setOperation()
697      */
698     public function run()
699     {
700         if ($this->_operation === '') {
701             throw new Crypt_GPG_InvalidOperationException('No GPG operation ' .
702                 'specified. Use Crypt_GPG_Engine::setOperation() before ' .
703                 'calling Crypt_GPG_Engine::run().');
704         }
705
706         $this->_openSubprocess();
707         $this->_process();
708         $this->_closeSubprocess();
709     }
710
711     // }}}
712     // {{{ getErrorCode()
713
714     /**
715      * Gets the error code of the last executed operation
716      *
717      * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
718      * been executed.
719      *
720      * @return integer the error code of the last executed operation.
721      */
722     public function getErrorCode()
723     {
724         return $this->_errorCode;
725     }
726
727     // }}}
728     // {{{ getErrorFilename()
729
730     /**
731      * Gets the file related to the error code of the last executed operation
732      *
733      * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
734      * been executed. If there is no file related to the error, an empty string
735      * is returned.
736      *
737      * @return string the file related to the error code of the last executed
738      *                operation.
739      */
740     public function getErrorFilename()
741     {
742         return $this->_errorFilename;
743     }
744
745     // }}}
746     // {{{ getErrorKeyId()
747
748     /**
749      * Gets the key id related to the error code of the last executed operation
750      *
751      * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
752      * been executed. If there is no key id related to the error, an empty
753      * string is returned.
754      *
755      * @return string the key id related to the error code of the last executed
756      *                operation.
757      */
758     public function getErrorKeyId()
759     {
760         return $this->_errorKeyId;
761     }
762
763     // }}}
764     // {{{ setInput()
765
766     /**
767      * Sets the input source for the current GPG operation
768      *
769      * @param string|resource &$input either a reference to the string
770      *                                containing the input data or an open
771      *                                stream resource containing the input
772      *                                data.
773      *
774      * @return void
775      */
776     public function setInput(&$input)
777     {
778         $this->_input =& $input;
779     }
780
781     // }}}
782     // {{{ setMessage()
783
784     /**
785      * Sets the message source for the current GPG operation
786      *
787      * Detached signature data should be specified here.
788      *
789      * @param string|resource &$message either a reference to the string
790      *                                  containing the message data or an open
791      *                                  stream resource containing the message
792      *                                  data.
793      *
794      * @return void
795      */
796     public function setMessage(&$message)
797     {
798         $this->_message =& $message;
799     }
800
801     // }}}
802     // {{{ setOutput()
803
804     /**
805      * Sets the output destination for the current GPG operation
806      *
807      * @param string|resource &$output either a reference to the string in
808      *                                 which to store GPG output or an open
809      *                                 stream resource to which the output data
810      *                                 should be written.
811      *
812      * @return void
813      */
814     public function setOutput(&$output)
815     {
816         $this->_output =& $output;
817     }
818
819     // }}}
820     // {{{ setOperation()
821
822     /**
823      * Sets the operation to perform
824      *
825      * @param string $operation the operation to perform. This should be one
826      *                          of GPG's operations. For example,
827      *                          <kbd>--encrypt</kbd>, <kbd>--decrypt</kbd>,
828      *                          <kbd>--sign</kbd>, etc.
829      * @param array  $arguments optional. Additional arguments for the GPG
830      *                          subprocess. See the GPG manual for specific
831      *                          values.
832      *
833      * @return void
834      *
835      * @see Crypt_GPG_Engine::reset()
836      * @see Crypt_GPG_Engine::run()
837      */
838     public function setOperation($operation, array $arguments = array())
839     {
840         $this->_operation = $operation;
841         $this->_arguments = $arguments;
842     }
843
844     // }}}
845     // {{{ getVersion()
846
847     /**
848      * Gets the version of the GnuPG binary
849      *
850      * @return string a version number string containing the version of GnuPG
851      *                being used. This value is suitable to use with PHP's
852      *                version_compare() function.
853      *
854      * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
855      *         Use the <kbd>debug</kbd> option and file a bug report if these
856      *         exceptions occur.
857      *
858      * @throws Crypt_GPG_UnsupportedException if the provided binary is not
859      *         GnuPG or if the GnuPG version is less than 1.0.2.
860      */
861     public function getVersion()
862     {
863         if ($this->_version == '') {
864
865             $options = array(
866                 'homedir' => $this->_homedir,
867                 'binary'  => $this->_binary,
868                 'debug'   => $this->_debug
869             );
870
871             $engine = new self($options);
872             $info   = '';
873
874             // Set a garbage version so we do not end up looking up the version
875             // recursively.
876             $engine->_version = '1.0.0';
877
878             $engine->reset();
879             $engine->setOutput($info);
880             $engine->setOperation('--version');
881             $engine->run();
882
883             $code = $this->getErrorCode();
884
885             if ($code !== Crypt_GPG::ERROR_NONE) {
886                 throw new Crypt_GPG_Exception(
887                     'Unknown error getting GnuPG version information. Please ' .
888                     'use the \'debug\' option when creating the Crypt_GPG ' .
889                     'object, and file a bug report at ' . Crypt_GPG::BUG_URI,
890                     $code);
891             }
892
893             $matches    = array();
894             $expression = '/gpg \(GnuPG\) (\S+)/';
895
896             if (preg_match($expression, $info, $matches) === 1) {
897                 $this->_version = $matches[1];
898             } else {
899                 throw new Crypt_GPG_Exception(
900                     'No GnuPG version information provided by the binary "' .
901                     $this->_binary . '". Are you sure it is GnuPG?');
902             }
903
904             if (version_compare($this->_version, self::MIN_VERSION, 'lt')) {
905                 throw new Crypt_GPG_Exception(
906                     'The version of GnuPG being used (' . $this->_version .
907                     ') is not supported by Crypt_GPG. The minimum version ' .
908                     'required by Crypt_GPG is ' . self::MIN_VERSION);
909             }
910         }
911
912
913         return $this->_version;
914     }
915
916     // }}}
917     // {{{ _handleErrorStatus()
918
919     /**
920      * Handles error values in the status output from GPG
921      *
922      * This method is responsible for setting the
923      * {@link Crypt_GPG_Engine::$_errorCode}. See <b>doc/DETAILS</b> in the
924      * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
925      * information on GPG's status output.
926      *
927      * @param string $line the status line to handle.
928      *
929      * @return void
930      */
931     private function _handleErrorStatus($line)
932     {
933         $tokens = explode(' ', $line);
934         switch ($tokens[0]) {
935         case 'BAD_PASSPHRASE':
936             $this->_errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
937             break;
938
939         case 'MISSING_PASSPHRASE':
940             $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
941             break;
942
943         case 'NODATA':
944             $this->_errorCode = Crypt_GPG::ERROR_NO_DATA;
945             break;
946
947         case 'DELETE_PROBLEM':
948             if ($tokens[1] == '1') {
949                 $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
950                 break;
951             } elseif ($tokens[1] == '2') {
952                 $this->_errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
953                 break;
954             }
955             break;
956
957         case 'IMPORT_RES':
958             if ($tokens[12] > 0) {
959                 $this->_errorCode = Crypt_GPG::ERROR_DUPLICATE_KEY;
960             }
961             break;
962
963         case 'NO_PUBKEY':
964         case 'NO_SECKEY':
965             $this->_errorKeyId = $tokens[1];
966             $this->_errorCode  = Crypt_GPG::ERROR_KEY_NOT_FOUND;
967             break;
968
969         case 'NEED_PASSPHRASE':
970             $this->_needPassphrase++;
971             break;
972
973         case 'GOOD_PASSPHRASE':
974             $this->_needPassphrase--;
975             break;
976
977         case 'EXPSIG':
978         case 'EXPKEYSIG':
979         case 'REVKEYSIG':
980         case 'BADSIG':
981             $this->_errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
982             break;
983
984         }
985     }
986
987     // }}}
988     // {{{ _handleErrorError()
989
990     /**
991      * Handles error values in the error output from GPG
992      *
993      * This method is responsible for setting the
994      * {@link Crypt_GPG_Engine::$_errorCode}.
995      *
996      * @param string $line the error line to handle.
997      *
998      * @return void
999      */
1000     private function _handleErrorError($line)
1001     {
1002         if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
1003             $pattern = '/no valid OpenPGP data found/';
1004             if (preg_match($pattern, $line) === 1) {
1005                 $this->_errorCode = Crypt_GPG::ERROR_NO_DATA;
1006             }
1007         }
1008
1009         if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
1010             $pattern = '/No secret key|secret key not available/';
1011             if (preg_match($pattern, $line) === 1) {
1012                 $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
1013             }
1014         }
1015
1016         if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
1017             $pattern = '/No public key|public key not found/';
1018             if (preg_match($pattern, $line) === 1) {
1019                 $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
1020             }
1021         }
1022
1023         if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
1024             $matches = array();
1025             $pattern = '/can\'t (?:access|open) `(.*?)\'/';
1026             if (preg_match($pattern, $line, $matches) === 1) {
1027                 $this->_errorFilename = $matches[1];
1028                 $this->_errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
1029             }
1030         }
1031     }
1032
1033     // }}}
1034     // {{{ _handleDebugStatus()
1035
1036     /**
1037      * Displays debug output for status lines
1038      *
1039      * @param string $line the status line to handle.
1040      *
1041      * @return void
1042      */
1043     private function _handleDebugStatus($line)
1044     {
1045         $this->_debug('STATUS: ' . $line);
1046     }
1047
1048     // }}}
1049     // {{{ _handleDebugError()
1050
1051     /**
1052      * Displays debug output for error lines
1053      *
1054      * @param string $line the error line to handle.
1055      *
1056      * @return void
1057      */
1058     private function _handleDebugError($line)
1059     {
1060         $this->_debug('ERROR: ' . $line);
1061     }
1062
1063     // }}}
1064     // {{{ _process()
1065
1066     /**
1067      * Performs internal streaming operations for the subprocess using either
1068      * strings or streams as input / output points
1069      *
1070      * This is the main I/O loop for streaming to and from the GPG subprocess.
1071      *
1072      * The implementation of this method is verbose mainly for performance
1073      * reasons. Adding streams to a lookup array and looping the array inside
1074      * the main I/O loop would be siginficantly slower for large streams.
1075      *
1076      * @return void
1077      *
1078      * @throws Crypt_GPG_Exception if there is an error selecting streams for
1079      *         reading or writing. If this occurs, please file a bug report at
1080      *         http://pear.php.net/bugs/report.php?package=Crypt_GPG.
1081      */
1082     private function _process()
1083     {
1084         $this->_debug('BEGIN PROCESSING');
1085
1086         $this->_commandBuffer = '';    // buffers input to GPG
1087         $messageBuffer        = '';    // buffers input to GPG
1088         $inputBuffer          = '';    // buffers input to GPG
1089         $outputBuffer         = '';    // buffers output from GPG
1090         $statusBuffer         = '';    // buffers output from GPG
1091         $errorBuffer          = '';    // buffers output from GPG
1092         $inputComplete        = false; // input stream is completely buffered
1093         $messageComplete      = false; // message stream is completely buffered
1094
1095         if (is_string($this->_input)) {
1096             $inputBuffer   = $this->_input;
1097             $inputComplete = true;
1098         }
1099
1100         if (is_string($this->_message)) {
1101             $messageBuffer   = $this->_message;
1102             $messageComplete = true;
1103         }
1104
1105         if (is_string($this->_output)) {
1106             $outputBuffer =& $this->_output;
1107         }
1108
1109         // convenience variables
1110         $fdInput   = $this->_pipes[self::FD_INPUT];
1111         $fdOutput  = $this->_pipes[self::FD_OUTPUT];
1112         $fdError   = $this->_pipes[self::FD_ERROR];
1113         $fdStatus  = $this->_pipes[self::FD_STATUS];
1114         $fdCommand = $this->_pipes[self::FD_COMMAND];
1115         $fdMessage = $this->_pipes[self::FD_MESSAGE];
1116
1117         while (true) {
1118
1119             $inputStreams     = array();
1120             $outputStreams    = array();
1121             $exceptionStreams = array();
1122
1123             // set up input streams
1124             if (is_resource($this->_input) && !$inputComplete) {
1125                 if (feof($this->_input)) {
1126                     $inputComplete = true;
1127                 } else {
1128                     $inputStreams[] = $this->_input;
1129                 }
1130             }
1131
1132             // close GPG input pipe if there is no more data
1133             if ($inputBuffer == '' && $inputComplete) {
1134                 $this->_debug('=> closing GPG input pipe');
1135                 $this->_closePipe(self::FD_INPUT);
1136             }
1137
1138             if (is_resource($this->_message) && !$messageComplete) {
1139                 if (feof($this->_message)) {
1140                     $messageComplete = true;
1141                 } else {
1142                     $inputStreams[] = $this->_message;
1143                 }
1144             }
1145
1146             // close GPG message pipe if there is no more data
1147             if ($messageBuffer == '' && $messageComplete) {
1148                 $this->_debug('=> closing GPG message pipe');
1149                 $this->_closePipe(self::FD_MESSAGE);
1150             }
1151
1152             if (!feof($fdOutput)) {
1153                 $inputStreams[] = $fdOutput;
1154             }
1155
1156             if (!feof($fdStatus)) {
1157                 $inputStreams[] = $fdStatus;
1158             }
1159
1160             if (!feof($fdError)) {
1161                 $inputStreams[] = $fdError;
1162             }
1163
1164             // set up output streams
1165             if ($outputBuffer != '' && is_resource($this->_output)) {
1166                 $outputStreams[] = $this->_output;
1167             }
1168
1169             if ($this->_commandBuffer != '') {
1170                 $outputStreams[] = $fdCommand;
1171             }
1172
1173             if ($messageBuffer != '') {
1174                 $outputStreams[] = $fdMessage;
1175             }
1176
1177             if ($inputBuffer != '') {
1178                 $outputStreams[] = $fdInput;
1179             }
1180
1181             // no streams left to read or write, we're all done
1182             if (count($inputStreams) === 0 && count($outputStreams) === 0) {
1183                 break;
1184             }
1185
1186             $this->_debug('selecting streams');
1187
1188             $ready = stream_select(
1189                 $inputStreams,
1190                 $outputStreams,
1191                 $exceptionStreams,
1192                 null
1193             );
1194
1195             $this->_debug('=> got ' . $ready);
1196
1197             if ($ready === false) {
1198                 throw new Crypt_GPG_Exception(
1199                     'Error selecting stream for communication with GPG ' .
1200                     'subprocess. Please file a bug report at: ' .
1201                     'http://pear.php.net/bugs/report.php?package=Crypt_GPG');
1202             }
1203
1204             if ($ready === 0) {
1205                 throw new Crypt_GPG_Exception(
1206                     'stream_select() returned 0. This can not happen! Please ' .
1207                     'file a bug report at: ' .
1208                     'http://pear.php.net/bugs/report.php?package=Crypt_GPG');
1209             }
1210
1211             // write input (to GPG)
1212             if (in_array($fdInput, $outputStreams)) {
1213                 $this->_debug('GPG is ready for input');
1214
1215                 $chunk = self::_byteSubstring(
1216                     $inputBuffer,
1217                     0,
1218                     self::CHUNK_SIZE
1219                 );
1220
1221                 $length = self::_byteLength($chunk);
1222
1223                 $this->_debug(
1224                     '=> about to write ' . $length . ' bytes to GPG input'
1225                 );
1226
1227                 $length = fwrite($fdInput, $chunk, $length);
1228
1229                 $this->_debug('=> wrote ' . $length . ' bytes');
1230
1231                 $inputBuffer = self::_byteSubstring(
1232                     $inputBuffer,
1233                     $length
1234                 );
1235             }
1236
1237             // read input (from PHP stream)
1238             if (in_array($this->_input, $inputStreams)) {
1239                 $this->_debug('input stream is ready for reading');
1240                 $this->_debug(
1241                     '=> about to read ' . self::CHUNK_SIZE .
1242                     ' bytes from input stream'
1243                 );
1244
1245                 $chunk        = fread($this->_input, self::CHUNK_SIZE);
1246                 $length       = self::_byteLength($chunk);
1247                 $inputBuffer .= $chunk;
1248
1249                 $this->_debug('=> read ' . $length . ' bytes');
1250             }
1251
1252             // write message (to GPG)
1253             if (in_array($fdMessage, $outputStreams)) {
1254                 $this->_debug('GPG is ready for message data');
1255
1256                 $chunk = self::_byteSubstring(
1257                     $messageBuffer,
1258                     0,
1259                     self::CHUNK_SIZE
1260                 );
1261
1262                 $length = self::_byteLength($chunk);
1263
1264                 $this->_debug(
1265                     '=> about to write ' . $length . ' bytes to GPG message'
1266                 );
1267
1268                 $length = fwrite($fdMessage, $chunk, $length);
1269                 $this->_debug('=> wrote ' . $length . ' bytes');
1270
1271                 $messageBuffer = self::_byteSubstring($messageBuffer, $length);
1272             }
1273
1274             // read message (from PHP stream)
1275             if (in_array($this->_message, $inputStreams)) {
1276                 $this->_debug('message stream is ready for reading');
1277                 $this->_debug(
1278                     '=> about to read ' . self::CHUNK_SIZE .
1279                     ' bytes from message stream'
1280                 );
1281
1282                 $chunk          = fread($this->_message, self::CHUNK_SIZE);
1283                 $length         = self::_byteLength($chunk);
1284                 $messageBuffer .= $chunk;
1285
1286                 $this->_debug('=> read ' . $length . ' bytes');
1287             }
1288
1289             // read output (from GPG)
1290             if (in_array($fdOutput, $inputStreams)) {
1291                 $this->_debug('GPG output stream ready for reading');
1292                 $this->_debug(
1293                     '=> about to read ' . self::CHUNK_SIZE .
1294                     ' bytes from GPG output'
1295                 );
1296
1297                 $chunk         = fread($fdOutput, self::CHUNK_SIZE);
1298                 $length        = self::_byteLength($chunk);
1299                 $outputBuffer .= $chunk;
1300
1301                 $this->_debug('=> read ' . $length . ' bytes');
1302             }
1303
1304             // write output (to PHP stream)
1305             if (in_array($this->_output, $outputStreams)) {
1306                 $this->_debug('output stream is ready for data');
1307
1308                 $chunk = self::_byteSubstring(
1309                     $outputBuffer,
1310                     0,
1311                     self::CHUNK_SIZE
1312                 );
1313
1314                 $length = self::_byteLength($chunk);
1315
1316                 $this->_debug(
1317                     '=> about to write ' . $length . ' bytes to output stream'
1318                 );
1319
1320                 $length = fwrite($this->_output, $chunk, $length);
1321
1322                 $this->_debug('=> wrote ' . $length . ' bytes');
1323
1324                 $outputBuffer = self::_byteSubstring($outputBuffer, $length);
1325             }
1326
1327             // read error (from GPG)
1328             if (in_array($fdError, $inputStreams)) {
1329                 $this->_debug('GPG error stream ready for reading');
1330                 $this->_debug(
1331                     '=> about to read ' . self::CHUNK_SIZE .
1332                     ' bytes from GPG error'
1333                 );
1334
1335                 $chunk        = fread($fdError, self::CHUNK_SIZE);
1336                 $length       = self::_byteLength($chunk);
1337                 $errorBuffer .= $chunk;
1338
1339                 $this->_debug('=> read ' . $length . ' bytes');
1340
1341                 // pass lines to error handlers
1342                 while (($pos = strpos($errorBuffer, PHP_EOL)) !== false) {
1343                     $line = self::_byteSubstring($errorBuffer, 0, $pos);
1344                     foreach ($this->_errorHandlers as $handler) {
1345                         array_unshift($handler['args'], $line);
1346                         call_user_func_array(
1347                             $handler['callback'],
1348                             $handler['args']
1349                         );
1350
1351                         array_shift($handler['args']);
1352                     }
1353                     $errorBuffer = self::_byteSubString(
1354                         $errorBuffer,
1355                         $pos + self::_byteLength(PHP_EOL)
1356                     );
1357                 }
1358             }
1359
1360             // read status (from GPG)
1361             if (in_array($fdStatus, $inputStreams)) {
1362                 $this->_debug('GPG status stream ready for reading');
1363                 $this->_debug(
1364                     '=> about to read ' . self::CHUNK_SIZE .
1365                     ' bytes from GPG status'
1366                 );
1367
1368                 $chunk         = fread($fdStatus, self::CHUNK_SIZE);
1369                 $length        = self::_byteLength($chunk);
1370                 $statusBuffer .= $chunk;
1371
1372                 $this->_debug('=> read ' . $length . ' bytes');
1373
1374                 // pass lines to status handlers
1375                 while (($pos = strpos($statusBuffer, PHP_EOL)) !== false) {
1376                     $line = self::_byteSubstring($statusBuffer, 0, $pos);
1377                     // only pass lines beginning with magic prefix
1378                     if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') {
1379                         $line = self::_byteSubstring($line, 9);
1380                         foreach ($this->_statusHandlers as $handler) {
1381                             array_unshift($handler['args'], $line);
1382                             call_user_func_array(
1383                                 $handler['callback'],
1384                                 $handler['args']
1385                             );
1386
1387                             array_shift($handler['args']);
1388                         }
1389                     }
1390                     $statusBuffer = self::_byteSubString(
1391                         $statusBuffer,
1392                         $pos + self::_byteLength(PHP_EOL)
1393                     );
1394                 }
1395             }
1396
1397             // write command (to GPG)
1398             if (in_array($fdCommand, $outputStreams)) {
1399                 $this->_debug('GPG is ready for command data');
1400
1401                 // send commands
1402                 $chunk = self::_byteSubstring(
1403                     $this->_commandBuffer,
1404                     0,
1405                     self::CHUNK_SIZE
1406                 );
1407
1408                 $length = self::_byteLength($chunk);
1409
1410                 $this->_debug(
1411                     '=> about to write ' . $length . ' bytes to GPG command'
1412                 );
1413
1414                 $length = fwrite($fdCommand, $chunk, $length);
1415
1416                 $this->_debug('=> wrote ' . $length);
1417
1418                 $this->_commandBuffer = self::_byteSubstring(
1419                     $this->_commandBuffer,
1420                     $length
1421                 );
1422             }
1423
1424         } // end loop while streams are open
1425
1426         $this->_debug('END PROCESSING');
1427     }
1428
1429     // }}}
1430     // {{{ _openSubprocess()
1431
1432     /**
1433      * Opens an internal GPG subprocess for the current operation
1434      *
1435      * Opens a GPG subprocess, then connects the subprocess to some pipes. Sets
1436      * the private class property {@link Crypt_GPG_Engine::$_process} to
1437      * the new subprocess.
1438      *
1439      * @return void
1440      *
1441      * @throws Crypt_GPG_OpenSubprocessException if the subprocess could not be
1442      *         opened.
1443      *
1444      * @see Crypt_GPG_Engine::setOperation()
1445      * @see Crypt_GPG_Engine::_closeSubprocess()
1446      * @see Crypt_GPG_Engine::$_process
1447      */
1448     private function _openSubprocess()
1449     {
1450         $version = $this->getVersion();
1451
1452         $env = $_ENV;
1453
1454         // Newer versions of GnuPG return localized results. Crypt_GPG only
1455         // works with English, so set the locale to 'C' for the subprocess.
1456         $env['LC_ALL'] = 'C';
1457
1458         $commandLine = $this->_binary;
1459
1460         $defaultArguments = array(
1461             '--status-fd ' . escapeshellarg(self::FD_STATUS),
1462             '--command-fd ' . escapeshellarg(self::FD_COMMAND),
1463             '--no-secmem-warning',
1464             '--no-tty',
1465             '--no-default-keyring', // ignored if keying files are not specified
1466             '--no-options'          // prevent creation of ~/.gnupg directory
1467         );
1468
1469         if (version_compare($version, '1.0.7', 'ge')) {
1470             if (version_compare($version, '2.0.0', 'lt')) {
1471                 $defaultArguments[] = '--no-use-agent';
1472             }
1473             $defaultArguments[] = '--no-permission-warning';
1474         }
1475
1476         if (version_compare($version, '1.4.2', 'ge')) {
1477             $defaultArguments[] = '--exit-on-status-write-error';
1478         }
1479
1480         if (version_compare($version, '1.3.2', 'ge')) {
1481             $defaultArguments[] = '--trust-model always';
1482         } else {
1483             $defaultArguments[] = '--always-trust';
1484         }
1485
1486         $arguments = array_merge($defaultArguments, $this->_arguments);
1487
1488         if ($this->_homedir) {
1489             $arguments[] = '--homedir ' . escapeshellarg($this->_homedir);
1490
1491             // the random seed file makes subsequent actions faster so only
1492             // disable it if we have to.
1493             if (!is_writeable($this->_homedir)) {
1494                 $arguments[] = '--no-random-seed-file';
1495             }
1496         }
1497
1498         if ($this->_publicKeyring) {
1499             $arguments[] = '--keyring ' . escapeshellarg($this->_publicKeyring);
1500         }
1501
1502         if ($this->_privateKeyring) {
1503             $arguments[] = '--secret-keyring ' .
1504                 escapeshellarg($this->_privateKeyring);
1505         }
1506
1507         if ($this->_trustDb) {
1508             $arguments[] = '--trustdb-name ' . escapeshellarg($this->_trustDb);
1509         }
1510
1511         $commandLine .= ' ' . implode(' ', $arguments) . ' ' .
1512             $this->_operation;
1513
1514         // Binary operations will not work on Windows with PHP < 5.2.6. This is
1515         // in case stream_select() ever works on Windows.
1516         $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
1517         $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';
1518
1519         $descriptorSpec = array(
1520             self::FD_INPUT   => array('pipe', $rb), // stdin
1521             self::FD_OUTPUT  => array('pipe', $wb), // stdout
1522             self::FD_ERROR   => array('pipe', $wb), // stderr
1523             self::FD_STATUS  => array('pipe', $wb), // status
1524             self::FD_COMMAND => array('pipe', $rb), // command
1525             self::FD_MESSAGE => array('pipe', $rb)  // message
1526         );
1527
1528         $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:');
1529         $this->_debug($commandLine);
1530
1531         $this->_process = proc_open(
1532             $commandLine,
1533             $descriptorSpec,
1534             $this->_pipes,
1535             null,
1536             $env,
1537             array('binary_pipes' => true)
1538         );
1539
1540         if (!is_resource($this->_process)) {
1541             throw new Crypt_GPG_OpenSubprocessException(
1542                 'Unable to open GPG subprocess.', 0, $commandLine);
1543         }
1544
1545         $this->_openPipes = $this->_pipes;
1546         $this->_errorCode = Crypt_GPG::ERROR_NONE;
1547     }
1548
1549     // }}}
1550     // {{{ _closeSubprocess()
1551
1552     /**
1553      * Closes a the internal GPG subprocess
1554      *
1555      * Closes the internal GPG subprocess. Sets the private class property
1556      * {@link Crypt_GPG_Engine::$_process} to null.
1557      *
1558      * @return void
1559      *
1560      * @see Crypt_GPG_Engine::_openSubprocess()
1561      * @see Crypt_GPG_Engine::$_process
1562      */
1563     private function _closeSubprocess()
1564     {
1565         if (is_resource($this->_process)) {
1566             $this->_debug('CLOSING SUBPROCESS');
1567
1568             // close remaining open pipes
1569             foreach (array_keys($this->_openPipes) as $pipeNumber) {
1570                 $this->_closePipe($pipeNumber);
1571             }
1572
1573             $exitCode = proc_close($this->_process);
1574
1575             if ($exitCode != 0) {
1576                 $this->_debug(
1577                     '=> subprocess returned an unexpected exit code: ' .
1578                     $exitCode
1579                 );
1580
1581                 if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
1582                     if ($this->_needPassphrase > 0) {
1583                         $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
1584                     } else {
1585                         $this->_errorCode = Crypt_GPG::ERROR_UNKNOWN;
1586                     }
1587                 }
1588             }
1589
1590             $this->_process = null;
1591             $this->_pipes   = array();
1592         }
1593     }
1594
1595     // }}}
1596     // {{{ _closePipe()
1597
1598     /**
1599      * Closes an opened pipe used to communicate with the GPG subprocess
1600      *
1601      * If the pipe is already closed, it is ignored. If the pipe is open, it
1602      * is flushed and then closed.
1603      *
1604      * @param integer $pipeNumber the file descriptor number of the pipe to
1605      *                            close.
1606      *
1607      * @return void
1608      */
1609     private function _closePipe($pipeNumber)
1610     {
1611         $pipeNumber = intval($pipeNumber);
1612         if (array_key_exists($pipeNumber, $this->_openPipes)) {
1613             fflush($this->_openPipes[$pipeNumber]);
1614             fclose($this->_openPipes[$pipeNumber]);
1615             unset($this->_openPipes[$pipeNumber]);
1616         }
1617     }
1618
1619     // }}}
1620     // {{{ _getBinary()
1621
1622     /**
1623      * Gets the name of the GPG binary for the current operating system
1624      *
1625      * This method is called if the '<kbd>binary</kbd>' option is <i>not</i>
1626      * specified when creating this driver.
1627      *
1628      * @return string the name of the GPG binary for the current operating
1629      *                system. If no suitable binary could be found, an empty
1630      *                string is returned.
1631      */
1632     private function _getBinary()
1633     {
1634         $binary = '';
1635
1636         if ($this->_isDarwin) {
1637             $binaryFiles = array(
1638                 '/opt/local/bin/gpg', // MacPorts
1639                 '/usr/local/bin/gpg', // Mac GPG
1640                 '/sw/bin/gpg',        // Fink
1641                 '/usr/bin/gpg'
1642             );
1643         } else {
1644             $binaryFiles = array(
1645                 '/usr/bin/gpg',
1646                 '/usr/local/bin/gpg'
1647             );
1648         }
1649
1650         foreach ($binaryFiles as $binaryFile) {
1651             if (is_executable($binaryFile)) {
1652                 $binary = $binaryFile;
1653                 break;
1654             }
1655         }
1656
1657         return $binary;
1658     }
1659
1660     // }}}
1661     // {{{ _debug()
1662
1663     /**
1664      * Displays debug text if debugging is turned on
1665      *
1666      * Debugging text is prepended with a debug identifier and echoed to stdout.
1667      *
1668      * @param string $text the debugging text to display.
1669      *
1670      * @return void
1671      */
1672     private function _debug($text)
1673     {
1674         if ($this->_debug) {
1675             if (array_key_exists('SHELL', $_ENV)) {
1676                 foreach (explode(PHP_EOL, $text) as $line) {
1677                     echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
1678                 }
1679             } else {
1680                 // running on a web server, format debug output nicely
1681                 foreach (explode(PHP_EOL, $text) as $line) {
1682                     echo "Crypt_GPG DEBUG: <strong>", $line,
1683                         '</strong><br />', PHP_EOL;
1684                 }
1685             }
1686         }
1687     }
1688
1689     // }}}
1690     // {{{ _byteLength()
1691
1692     /**
1693      * Gets the length of a string in bytes even if mbstring function
1694      * overloading is turned on
1695      *
1696      * This is used for stream-based communication with the GPG subprocess.
1697      *
1698      * @param string $string the string for which to get the length.
1699      *
1700      * @return integer the length of the string in bytes.
1701      *
1702      * @see Crypt_GPG_Engine::$_mbStringOverload
1703      */
1704     private static function _byteLength($string)
1705     {
1706         if (self::$_mbStringOverload) {
1707             return mb_strlen($string, '8bit');
1708         }
1709
1710         return strlen((binary)$string);
1711     }
1712
1713     // }}}
1714     // {{{ _byteSubstring()
1715
1716     /**
1717      * Gets the substring of a string in bytes even if mbstring function
1718      * overloading is turned on
1719      *
1720      * This is used for stream-based communication with the GPG subprocess.
1721      *
1722      * @param string  $string the input string.
1723      * @param integer $start  the starting point at which to get the substring.
1724      * @param integer $length optional. The length of the substring.
1725      *
1726      * @return string the extracted part of the string. Unlike the default PHP
1727      *                <kbd>substr()</kbd> function, the returned value is
1728      *                always a string and never false.
1729      *
1730      * @see Crypt_GPG_Engine::$_mbStringOverload
1731      */
1732     private static function _byteSubstring($string, $start, $length = null)
1733     {
1734         if (self::$_mbStringOverload) {
1735             if ($length === null) {
1736                 return mb_substr(
1737                     $string,
1738                     $start,
1739                     self::_byteLength($string) - $start, '8bit'
1740                 );
1741             }
1742
1743             return mb_substr($string, $start, $length, '8bit');
1744         }
1745
1746         if ($length === null) {
1747             return (string)substr((binary)$string, $start);
1748         }
1749
1750         return (string)substr((binary)$string, $start, $length);
1751     }
1752
1753     // }}}
1754 }
1755
1756 // }}}
1757
1758 ?>