Crypt_GPG
[ class tree: Crypt_GPG ] [ index: Crypt_GPG ] [ all elements ]

Source for file PinEntry.php

Documentation is available at PinEntry.php

  1. <?php
  2.  
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4.  
  5. /**
  6.  * Contains a class implementing automatic pinentry for gpg-agent
  7.  *
  8.  * PHP version 5
  9.  *
  10.  * LICENSE:
  11.  *
  12.  * This library is free software; you can redistribute it and/or modify
  13.  * it under the terms of the GNU Lesser General Public License as
  14.  * published by the Free Software Foundation; either version 2.1 of the
  15.  * License, or (at your option) any later version.
  16.  *
  17.  * This library is distributed in the hope that it will be useful,
  18.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  20.  * Lesser General Public License for more details.
  21.  *
  22.  * You should have received a copy of the GNU Lesser General Public
  23.  * License along with this library; if not, write to the Free Software
  24.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  25.  *
  26.  * @category  Encryption
  27.  * @package   Crypt_GPG
  28.  * @author    Michael Gauthier <mike@silverorange.com>
  29.  * @copyright 2013 silverorange
  30.  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
  31.  * @version   CVS: $Id$
  32.  * @link      http://pear.php.net/package/Crypt_GPG
  33.  */
  34.  
  35. /**
  36.  * CLI user-interface and parser.
  37.  */
  38. require_once 'Console/CommandLine.php';
  39.  
  40. // {{{ class Crypt_GPG_PinEntry
  41.  
  42. /**
  43.  * A command-line dummy pinentry program for use with gpg-agent and Crypt_GPG
  44.  *
  45.  * This pinentry receives passphrases through en environment variable and
  46.  * automatically enters the PIN in response to gpg-agent requests. No user-
  47.  * interaction required.
  48.  *
  49.  * Thie pinentry can be run independently for testing and debugging with the
  50.  * following syntax:
  51.  *
  52.  * <pre>
  53.  * Usage:
  54.  *   crypt-gpg-pinentry [options]
  55.  *
  56.  * Options:
  57.  *   -l log, --log=log  Optional location to log pinentry activity.
  58.  *   -v, --verbose      Sets verbosity level. Use multiples for more detail
  59.  *                      (e.g. "-vv").
  60.  *   -h, --help         show this help message and exit
  61.  *   --version          show the program version and exit
  62.  * </pre>
  63.  *
  64.  * @category  Encryption
  65.  * @package   Crypt_GPG
  66.  * @author    Michael Gauthier <mike@silverorange.com>
  67.  * @copyright 2013 silverorange
  68.  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
  69.  * @link      http://pear.php.net/package/Crypt_GPG
  70.  * @see       Crypt_GPG::getKeys()
  71.  */
  72. {
  73.     // {{{ class constants
  74.  
  75.     /**
  76.      * Verbosity level for showing no output.
  77.      */
  78.     const VERBOSITY_NONE = 0;
  79.  
  80.     /**
  81.      * Verbosity level for showing error output.
  82.      */
  83.     const VERBOSITY_ERRORS = 1;
  84.  
  85.     /**
  86.      * Verbosity level for showing all output, including Assuan protocol
  87.      * messages.
  88.      */
  89.     const VERBOSITY_ALL = 2;
  90.  
  91.     /**
  92.      * Length of buffer for reading lines from the Assuan server.
  93.      *
  94.      * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192
  95.      * and buffers the rest so we might as well just read 8192.
  96.      *
  97.      * Using values other than 8192 also triggers PHP bugs.
  98.      *
  99.      * @see http://bugs.php.net/bug.php?id=35224
  100.      */
  101.     const CHUNK_SIZE = 8192;
  102.  
  103.     // }}}
  104.     // {{{ protected properties
  105.  
  106.     /**
  107.      * File handle for the input stream
  108.      *
  109.      * @var resource 
  110.      */
  111.     protected $stdin = null;
  112.  
  113.     /**
  114.      * File handle for the output stream
  115.      *
  116.      * @var resource 
  117.      */
  118.     protected $stdout = null;
  119.  
  120.     /**
  121.      * File handle for the log file if a log file is used
  122.      *
  123.      * @var resource 
  124.      */
  125.     protected $logFile = null;
  126.  
  127.     /**
  128.      * Whether or not this pinentry is finished and is exiting
  129.      *
  130.      * @var boolean 
  131.      */
  132.     protected $moribund = false;
  133.  
  134.     /**
  135.      * Verbosity level
  136.      *
  137.      * One of:
  138.      * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE},
  139.      * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS}, or
  140.      * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL}
  141.      *
  142.      * @var integer 
  143.      */
  144.     protected $verbosity = self::VERBOSITY_NONE;
  145.  
  146.     /**
  147.      * The command-line interface parser for this pinentry
  148.      *
  149.      * @var Console_CommandLine 
  150.      *
  151.      * @see Crypt_GPG_PinEntry::getParser()
  152.      */
  153.     protected $parser = null;
  154.  
  155.     /**
  156.      * PINs to be entered by this pinentry
  157.      *
  158.      * An indexed array of associative arrays in the form:
  159.      * <code>
  160.      * <?php
  161.      *   array(
  162.      *     array(
  163.      *       'keyId'      => $keyId,
  164.      *       'passphrase' => $passphrase
  165.      *     ),
  166.      *     ...
  167.      *   );
  168.      * ?>
  169.      * </code>
  170.      *
  171.      * This array is parsed from the environment variable
  172.      * <kbd>PINENTRY_USER_DATA</kbd>.
  173.      *
  174.      * @var array 
  175.      *
  176.      * @see Crypt_GPG_PinEntry::initPinsFromENV()
  177.      */
  178.     protected $pins = array();
  179.  
  180.     /**
  181.      * PINs that have been tried for the current PIN
  182.      *
  183.      * This is an associative array indexed by the key identifier with
  184.      * values being the same as elements in the {@link Crypt_GPG_PinEntry::$pins}
  185.      * array.
  186.      *
  187.      * @var array 
  188.      */
  189.     protected $triedPins = array();
  190.  
  191.     /**
  192.      * The PIN currently being requested by the Assuan server
  193.      *
  194.      * If set, this is an associative array in the form:
  195.      * <code>
  196.      * <?php
  197.      *   array(
  198.      *     'keyId'  => $shortKeyId,
  199.      *     'userId' => $userIdString
  200.      *   );
  201.      * ?>
  202.      * </code>
  203.      *
  204.      * @var array|null
  205.      */
  206.     protected $currentPin = null;
  207.  
  208.     // }}}
  209.     // {{{ __invoke()
  210.  
  211.     /**
  212.      * Runs this pinentry
  213.      *
  214.      * @return void 
  215.      */
  216.     public function __invoke()
  217.     {
  218.         $this->parser = $this->getCommandLineParser();
  219.  
  220.         try {
  221.             $result $this->parser->parse();
  222.  
  223.             $this->setVerbosity($result->options['verbose']);
  224.             $this->setLogFilename($result->options['log']);
  225.  
  226.             $this->connect();
  227.             $this->initPinsFromENV();
  228.  
  229.             while (($line fgets($this->stdinself::CHUNK_SIZE)) !== false{
  230.                 $this->parseCommand(mb_substr($line0-1'8bit'));
  231.                 if ($this->moribund{
  232.                     break;
  233.                 }
  234.             }
  235.  
  236.             $this->disconnect();
  237.  
  238.         catch (Console_CommandLineException $e{
  239.             $this->log($e->getMessage(. PHP_EOLslf::VERBOSITY_ERRORS);
  240.             exit(1);
  241.         catch (Exception $e{
  242.             $this->log($e->getMessage(. PHP_EOLself::VERBOSITY_ERRORS);
  243.             $this->log($e->getTraceAsString(. PHP_EOLself::VERBOSITY_ERRORS);
  244.             exit(1);
  245.         }
  246.     }
  247.  
  248.     // }}}
  249.     // {{{ setVerbosity()
  250.  
  251.     /**
  252.      * Sets the verbosity of logging for this pinentry
  253.      *
  254.      * Verbosity levels are:
  255.      *
  256.      * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE}   - no logging.
  257.      * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS} - log errors only.
  258.      * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL}    - log everything, including
  259.      *                                                  the assuan protocol.
  260.      *
  261.      * @param integer $verbosity the level of verbosity of this pinentry.
  262.      *
  263.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  264.      */
  265.     public function setVerbosity($verbosity)
  266.     {
  267.         $this->verbosity = (integer)$verbosity;
  268.         return $this;
  269.     }
  270.  
  271.     // }}}
  272.     // {{{ setLogFilename()
  273.  
  274.     /**
  275.      * Sets the log file location
  276.      *
  277.      * @param string $filename the new log filename to use. If an empty string
  278.      *                          is used, file-based logging is disabled.
  279.      *
  280.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  281.      */
  282.     public function setLogFilename($filename)
  283.     {
  284.         if (is_resource($this->logFile)) {
  285.             fflush($this->logFile);
  286.             fclose($this->logFile);
  287.             $this->logFile = null;
  288.         }
  289.  
  290.         if ($filename != ''{
  291.             if (($this->logFile = fopen($filename'w')) === false{
  292.                 $this->log(
  293.                     'Unable to open log file "' $filename '" '
  294.                     . 'for writing.' . PHP_EOL,
  295.                     self::VERBOSITY_ERRORS
  296.                 );
  297.                 exit(1);
  298.             else {
  299.                 stream_set_write_buffer($this->logFile0);
  300.             }
  301.         }
  302.  
  303.         return $this;
  304.     }
  305.  
  306.     // }}}
  307.     // {{{ getUIXML()
  308.  
  309.     /**
  310.      * Gets the CLI user-interface definition for this pinentry
  311.      *
  312.      * Detects whether or not this package is PEAR-installed and appropriately
  313.      * locates the XML UI definition.
  314.      *
  315.      * @return string the location of the CLI user-interface definition XML.
  316.      */
  317.     protected function getUIXML()
  318.     {
  319.         $dir '@data-dir@' . DIRECTORY_SEPARATOR
  320.             . '@package-name@' . DIRECTORY_SEPARATOR . 'data';
  321.  
  322.         // Check if we're running directly from a git checkout or if we're
  323.         // running from a PEAR-packaged version.
  324.         if ($dir[0== '@'{
  325.             $dir dirname(__FILE__. DIRECTORY_SEPARATOR . '..'
  326.                 . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data';
  327.         }
  328.  
  329.         return $dir . DIRECTORY_SEPARATOR . 'pinentry-cli.xml';
  330.     }
  331.  
  332.     // }}}
  333.     // {{{ getCommandLineParser()
  334.  
  335.     /**
  336.      * Gets the CLI parser for this pinentry
  337.      *
  338.      * @return Console_CommandLine the CLI parser for this pinentry.
  339.      */
  340.     protected function getCommandLineParser()
  341.     {
  342.         return Console_CommandLine::fromXmlFile($this->getUIXML());
  343.     }
  344.  
  345.     // }}}
  346.     // {{{ log()
  347.  
  348.     /**
  349.      * Logs a message at the specified verbosity level
  350.      *
  351.      * If a log file is used, the message is written to the log. Otherwise,
  352.      * the message is sent to STDERR.
  353.      *
  354.      * @param string  $data  the message to log.
  355.      * @param integer $level the verbosity level above which the message should
  356.      *                        be logged.
  357.      *
  358.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  359.      */
  360.     protected function log($data$level)
  361.     {
  362.         if ($this->verbosity >= $level{
  363.             if (is_resource($this->logFile)) {
  364.                 fwrite($this->logFile$data);
  365.                 fflush($this->logFile);
  366.             else {
  367.                 $this->parser->outputter->stderr($data);
  368.             }
  369.         }
  370.  
  371.         return $this;
  372.     }
  373.  
  374.     // }}}
  375.     // {{{ connect()
  376.  
  377.     /**
  378.      * Connects this pinentry to the assuan server
  379.      *
  380.      * Opens I/O streams and sends initial handshake.
  381.      *
  382.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  383.      */
  384.     protected function connect()
  385.     {
  386.         // Binary operations will not work on Windows with PHP < 5.2.6.
  387.         $rb (version_compare(PHP_VERSION'5.2.6'< 0'r' 'rb';
  388.         $wb (version_compare(PHP_VERSION'5.2.6'< 0'w' 'wb';
  389.  
  390.         $this->stdin  = fopen('php://stdin'$rb);
  391.         $this->stdout = fopen('php://stdout'$wb);
  392.  
  393.         if (function_exists('stream_set_read_buffer')) {
  394.             stream_set_read_buffer($this->stdin0);
  395.         }
  396.         stream_set_write_buffer($this->stdout0);
  397.  
  398.         // initial handshake
  399.         $this->send($this->getOK('Crypt_GPG pinentry ready and waiting'));
  400.  
  401.         return $this;
  402.     }
  403.  
  404.     // }}}
  405.     // {{{ parseCommand()
  406.  
  407.     /**
  408.      * Parses an assuan command and performs the appropriate action
  409.      *
  410.      * Documentation of the assuan commands for pinentry is limited to
  411.      * non-existent. Most of these commands were taken from the C source code
  412.      * to gpg-agent and pinentry.
  413.      *
  414.      * Additional context was provided by using strace -f when calling the
  415.      * gpg-agent.
  416.      *
  417.      * @param string $line the assuan command line to parse
  418.      *
  419.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  420.      */
  421.     protected function parseCommand($line)
  422.     {
  423.         $this->log('<- ' $line . PHP_EOLself::VERBOSITY_ALL);
  424.  
  425.         $parts explode(' '$line2);
  426.  
  427.         $command $parts[0];
  428.  
  429.         if (count($parts=== 2{
  430.             $data $parts[1];
  431.         else {
  432.             $data = null;
  433.         }
  434.  
  435.         switch ($command{
  436.         case 'SETDESC':
  437.             return $this->sendSetDescription($data);
  438.  
  439.         case 'SETPROMPT':
  440.         case 'SETERROR':
  441.         case 'SETOK':
  442.         case 'SETNOTOK':
  443.         case 'SETCANCEL':
  444.         case 'SETQUALITYBAR':
  445.         case 'SETQUALITYBAR_TT':
  446.         case 'OPTION':
  447.             return $this->sendNotImplementedOK();
  448.  
  449.         case 'MESSAGE':
  450.             return $this->sendMessage();
  451.  
  452.         case 'CONFIRM':
  453.             return $this->sendConfirm();
  454.  
  455.         case 'GETINFO':
  456.             return $this->sendGetInfo($data);
  457.  
  458.         case 'GETPIN':
  459.             return $this->sendGetPin($data);
  460.  
  461.         case 'RESET':
  462.             return $this->sendReset();
  463.  
  464.         case 'BYE':
  465.             return $this->sendBye();
  466.         }
  467.     }
  468.  
  469.     // }}}
  470.     // {{{ initPinsFromENV()
  471.  
  472.     /**
  473.      * Initializes the PINs to be entered by this pinentry from the environment
  474.      * variable PINENTRY_USER_DATA
  475.      *
  476.      * The PINs are parsed from a JSON-encoded string.
  477.      *
  478.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  479.      */
  480.     protected function initPinsFromENV()
  481.     {
  482.         if (($userData getenv('PINENTRY_USER_DATA')) !== false{
  483.             $pins json_decode($userDatatrue);
  484.             if ($pins === null{
  485.                 $this->log(
  486.                     '-- failed to parse user data' . PHP_EOL,
  487.                     self::VERBOSITY_ERRORS
  488.                 );
  489.             else {
  490.                 $this->pins = $pins;
  491.                 $this->log(
  492.                     '-- got user data [not showing passphrases]' . PHP_EOL,
  493.                     self::VERBOSITY_ALL
  494.                 );
  495.             }
  496.         }
  497.  
  498.         return $this;
  499.     }
  500.  
  501.     // }}}
  502.     // {{{ disconnect()
  503.  
  504.     /**
  505.      * Disconnects this pinentry from the Assuan server
  506.      *
  507.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  508.      */
  509.     protected function disconnect()
  510.     {
  511.         $this->log('-- disconnecting' . PHP_EOLself::VERBOSITY_ALL);
  512.  
  513.         fflush($this->stdout);
  514.         fclose($this->stdout);
  515.         fclose($this->stdin);
  516.  
  517.         $this->stdin  = null;
  518.         $this->stdout = null;
  519.  
  520.         $this->log('-- disconnected' . PHP_EOLself::VERBOSITY_ALL);
  521.  
  522.         if (is_resource($this->logFile)) {
  523.             fflush($this->logFile);
  524.             fclose($this->logFile);
  525.             $this->logFile = null;
  526.         }
  527.  
  528.         return $this;
  529.     }
  530.  
  531.     // }}}
  532.     // {{{ sendNotImplementedOK()
  533.  
  534.     /**
  535.      * Sends an OK response for a not implemented feature
  536.      *
  537.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  538.      */
  539.     protected function sendNotImplementedOK()
  540.     {
  541.         return $this->send($this->getOK());
  542.     }
  543.  
  544.     // }}}
  545.     // {{{ sendSetDescription()
  546.  
  547.     /**
  548.      * Parses the currently requested key identifier and user identifier from
  549.      * the description passed to this pinentry
  550.      *
  551.      * @param string $text the raw description sent from gpg-agent.
  552.      *
  553.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  554.      */
  555.     protected function sendSetDescription($text)
  556.     {
  557.         $text rawurldecode($text);
  558.         $matches = array();
  559.         // TODO: handle user id with quotation marks
  560.         $exp '/\n"(.+)"\n.*\sID ([A-Z0-9]+),\n/mu';
  561.         if (preg_match($exp$text$matches=== 1{
  562.             $userId $matches[1];
  563.             $keyId  $matches[2];
  564.  
  565.             // only reset tried pins for new requested pin
  566.             if (   $this->currentPin === null
  567.                 || $this->currentPin['keyId'!== $keyId
  568.             {
  569.                 $this->currentPin = array(
  570.                     'userId' => $userId,
  571.                     'keyId'  => $keyId
  572.                 );
  573.                 $this->triedPins = array();
  574.                 $this->log(
  575.                     '-- looking for PIN for ' $keyId . PHP_EOL,
  576.                     self::VERBOSITY_ALL
  577.                 );
  578.             }
  579.         }
  580.  
  581.         return $this->send($this->getOK());
  582.     }
  583.  
  584.     // }}}
  585.     // {{{ sendConfirm()
  586.  
  587.     /**
  588.      * Tells the assuan server the PIN entry was confirmed (not cancelled)
  589.      * by pressing the fake 'close' button
  590.      *
  591.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  592.      */
  593.     protected function sendConfirm()
  594.     {
  595.         return $this->sendButtonInfo('close');
  596.     }
  597.  
  598.     // }}}
  599.     // {{{ sendMessage()
  600.  
  601.     /**
  602.      * Tells the assuan server that any requested pop-up messages were confirmed
  603.      * by pressing the fake 'close' button
  604.      *
  605.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  606.      */
  607.     protected function sendMessage()
  608.     {
  609.         return $this->sendButtonInfo('close');
  610.     }
  611.  
  612.     // }}}
  613.     // {{{ sendButtonInfo()
  614.  
  615.     /**
  616.      * Sends information about pressed buttons to the assuan server
  617.      *
  618.      * This is used to fake a user-interface for this pinentry.
  619.      *
  620.      * @param string $text the button status to send.
  621.      *
  622.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  623.      */
  624.     protected function sendButtonInfo($text)
  625.     {
  626.         return $this->send('BUTTON_INFO ' $text "\n");
  627.     }
  628.  
  629.     // }}}
  630.     // {{{ sendGetPin()
  631.  
  632.     /**
  633.      * Sends the PIN value for the currently requested key
  634.      *
  635.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  636.      */
  637.     protected function sendGetPin()
  638.     {
  639.         $foundPin '';
  640.  
  641.         if (is_array($this->currentPin)) {
  642.             $keyIdLength mb_strlen($this->currentPin['keyId']'8bit');
  643.  
  644.             // search for the pin
  645.             foreach ($this->pins as $pin{
  646.                 // only check pins we haven't tried
  647.                 if (!isset($this->triedPins[$pin['keyId']])) {
  648.  
  649.                     // get last X characters of key identifier to compare
  650.                     $keyId mb_substr(
  651.                         $pin['keyId'],
  652.                         -$keyIdLength,
  653.                         mb_strlen($pin['keyId']'8bit'),
  654.                         '8bit'
  655.                     );
  656.  
  657.                     if ($keyId === $this->currentPin['keyId']{
  658.                         $foundPin $pin['passphrase'];
  659.                         $this->triedPins[$pin['keyId']] $pin;
  660.                         break;
  661.                     }
  662.                 }
  663.             }
  664.         }
  665.  
  666.         return $this
  667.             ->send($this->getData($foundPin))
  668.             ->send($this->getOK());
  669.     }
  670.  
  671.     // }}}
  672.     // {{{ sendGetInfo()
  673.  
  674.     /**
  675.      * Sends information about this pinentry
  676.      *
  677.      * @param string $data the information requested by the assuan server.
  678.      *                      Currently only 'pid' is supported. Other requests
  679.      *                      return no information.
  680.      *
  681.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  682.      */
  683.     protected function sendGetInfo($data)
  684.     {
  685.         $parts   explode(' '$data2);
  686.         $command reset($parts);
  687.  
  688.         switch ($command{
  689.         case 'pid':
  690.             return $this->sendGetInfoPID();
  691.         default:
  692.             return $this->send($this->getOK());
  693.         }
  694.  
  695.         return $this;
  696.     }
  697.     // }}}
  698.     // {{{ sendGetInfoPID()
  699.  
  700.     /**
  701.      * Sends the PID of this pinentry to the assuan server
  702.      *
  703.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  704.      */
  705.     protected function sendGetInfoPID()
  706.     {
  707.         return $this
  708.             ->send($this->getData(getmypid()))
  709.             ->send($this->getOK());
  710.     }
  711.  
  712.     // }}}
  713.     // {{{ sendBye()
  714.  
  715.     /**
  716.      * Flags this pinentry for disconnection and sends an OK response
  717.      *
  718.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  719.      */
  720.     protected function sendBye()
  721.     {
  722.         $return $this->send($this->getOK('closing connection'));
  723.         $this->moribund = true;
  724.         return $return;
  725.     }
  726.  
  727.     // }}}
  728.     // {{{ sendReset()
  729.  
  730.     /**
  731.      * Resets this pinentry and sends an OK response
  732.      *
  733.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  734.      */
  735.     protected function sendReset()
  736.     {
  737.         $this->currentPin = null;
  738.         $this->triedPins = array();
  739.         return $this->send($this->getOK());
  740.     }
  741.  
  742.     // }}}
  743.     // {{{ getOK()
  744.  
  745.     /**
  746.      * Gets an OK response to send to the assuan server
  747.      *
  748.      * @param string $data an optional message to include with the OK response.
  749.      *
  750.      * @return string the OK response.
  751.      */
  752.     protected function getOK($data = null)
  753.     {
  754.         $return 'OK';
  755.  
  756.         if ($data{
  757.             $return .= ' ' $data;
  758.         }
  759.  
  760.         return $return "\n";
  761.     }
  762.  
  763.     // }}}
  764.     // {{{ getData()
  765.  
  766.     /**
  767.      * Gets data ready to send to the assuan server
  768.      *
  769.      * Data is appropriately escaped and long lines are wrapped.
  770.      *
  771.      * @param string $data the data to send to the assuan server.
  772.      *
  773.      * @return string the properly escaped, formatted data.
  774.      *
  775.      * @see  http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
  776.      */
  777.     protected function getData($data)
  778.     {
  779.         // Escape data. Only %, \n and \r need to be escaped but other
  780.         // values are allowed to be escaped. See
  781.         // http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
  782.         $data rawurlencode($data);
  783.         $data $this->getWordWrappedData($data'D');
  784.         return $data;
  785.     }
  786.  
  787.     // }}}
  788.     // {{{ getComment()
  789.  
  790.     /**
  791.      * Gets a comment ready to send to the assuan server
  792.      *
  793.      * @param string $data the comment to send to the assuan server.
  794.      *
  795.      * @return string the properly formatted comment.
  796.      *
  797.      * @see  http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
  798.      */
  799.     protected function getComment($data)
  800.     {
  801.         return $this->getWordWrappedData($data'#');
  802.     }
  803.  
  804.     // }}}
  805.     // {{{ getWordWrappedData()
  806.  
  807.     /**
  808.      * Wraps strings at 1,000 bytes without splitting UTF-8 multibyte
  809.      * characters
  810.      *
  811.      * Each line is prepended with the specified line prefix. Wrapped lines
  812.      * are automatically appended with \ characters.
  813.      *
  814.      * Protocol strings are UTF-8 but maximum line length is 1,000 bytes.
  815.      * <kbd>mb_strcut()</kbd> is used so we can limit line length by bytes
  816.      * and not split characters across multiple lines.
  817.      *
  818.      * @param string $data   the data to wrap.
  819.      * @param string $prefix a single character to use as the line prefix. For
  820.      *                        example, 'D' or '#'.
  821.      *
  822.      * @return string the word-wrapped, prefixed string.
  823.      *
  824.      * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
  825.      */
  826.     protected function getWordWrappedData($data$prefix)
  827.     {
  828.         $lines = array();
  829.  
  830.         do {
  831.             if (mb_strlen($data'8bit'> 997{
  832.                 $line $prefix ' ' mb_strcut($data0996'utf-8'"\\\n";
  833.                 $lines[$line;
  834.                 $lineLength mb_strlen($line'8bit'- 1;
  835.                 $dataLength mb_substr($data'8bit');
  836.                 $data mb_substr(
  837.                     $data,
  838.                     $lineLength,
  839.                     $dataLength $lineLength,
  840.                     '8bit'
  841.                 );
  842.             else {
  843.                 $lines[$prefix ' ' $data "\n";
  844.                 $data '';
  845.             }
  846.         while ($data != '');
  847.  
  848.         return implode(''$lines);
  849.     }
  850.  
  851.     // }}}
  852.     // {{{ send()
  853.  
  854.     /**
  855.      * Sends raw data to the assuan server
  856.      *
  857.      * @param string $data the data to send.
  858.      *
  859.      * @return Crypt_GPG_PinEntry the current object, for fluent interface.
  860.      */
  861.     protected function send($data)
  862.     {
  863.         $this->log('-> ' $dataself::VERBOSITY_ALL);
  864.         fwrite($this->stdout$data);
  865.         fflush($this->stdout);
  866.         return $this;
  867.     }
  868.  
  869.     // }}}
  870. }
  871.  
  872. // }}}
  873.  
  874. ?>

Documentation generated on Wed, 13 Mar 2013 18:30:11 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.