public function __construct($cipher, $key, $taglen) { $logging = \Plop\Plop::getInstance(); $this->cipher = mcrypt_module_open($cipher, null, 'ecb', null); mcrypt_generic_init($this->cipher, $key, str_repeat("", 16)); $this->taglen = $taglen; $logging->debug('Pre-computing GCM table'); $H = gmp_init(bin2hex(mcrypt_generic($this->cipher, str_repeat("", 16))), 16); $H = str_pad(gmp_strval($H, 2), 128, '0', STR_PAD_LEFT); $R = gmp_init('E1000000000000000000000000000000', 16); $this->table = array(); for ($i = 0; $i < 16; $i++) { $this->table[$i] = array(); for ($j = 0; $j < 256; $j++) { $V = gmp_init(dechex($j) . str_repeat("00", $i), 16); $Z = gmp_init(0); for ($k = 0; $k < 128; $k++) { // Compute Z_n+1 if ($H[$k]) { $Z = gmp_xor($Z, $V); } // Compute V_n+1 $odd = gmp_testbit($V, 0); $V = gmp_div_q($V, 2); if ($odd) { $V = gmp_xor($V, $R); } } $this->table[$i][$j] = pack('H*', str_pad(gmp_strval($Z, 16), 32, 0, STR_PAD_LEFT)); } } $logging->debug('Done pre-computing GCM table'); }
public function authenticate(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base $message, \fpoirotte\Pssht\Transport $transport, array &$context) { if (!$message instanceof \fpoirotte\Pssht\Messages\USERAUTH\REQUEST\PublicKey) { throw new \InvalidArgumentException(); } if ($message->getSignature() === null) { return self::AUTH_REJECT; } $logging = \Plop\Plop::getInstance(); $reverse = gethostbyaddr($transport->getAddress()); $algos = \fpoirotte\Pssht\Algorithms::factory(); $cls = $algos->getClass('PublicKey', $message->getAlgorithm()); if ($cls === null || !$this->store->exists($message->getUserName(), $message->getKey())) { $logging->info('Rejected public key connection from remote host "%(reverse)s" ' . 'to "%(luser)s" (unsupported key)', array('luser' => escape($message->getUserName()), 'reverse' => $reverse)); return self::AUTH_REJECT; } $key = $cls::loadPublic(base64_encode($message->getKey())); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeString($context['DH']->getExchangeHash()); $encoder->encodeBytes(chr(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base::getMessageId())); $encoder->encodeString($message->getUserName()); $encoder->encodeString($message->getServiceName()); $encoder->encodeString(static::getName()); $encoder->encodeBoolean(true); $encoder->encodeString($message->getAlgorithm()); $encoder->encodeString($message->getKey()); if ($key->check($encoder->getBuffer()->get(0), $message->getSignature())) { $logging->info('Accepted public key connection from remote host "%(reverse)s" ' . 'to "%(luser)s" (using "%(algorithm)s" algorithm)', array('luser' => escape($message->getUserName()), 'reverse' => $reverse, 'algorithm' => escape($message->getAlgorithm()))); return self::AUTH_ACCEPT; } $logging->info('Rejected public key connection from remote host "%(reverse)s" ' . 'to "%(luser)s" (invalid signature)', array('luser' => escape($message->getUserName()), 'reverse' => $reverse)); return self::AUTH_REJECT; }
public function log($level, $message, array $context = array()) { /*** * \note * There are several contexts in which the follow code * may not work as expected. * * Using threads (https://github.com/krakjoe/pthreads) * with this method will probably mess things up. * * The following functions may also cause trouble * depending on how they're used: * - register_tick_function() * - debug_backtrace() */ $logging = \Plop\Plop::getInstance(); $factory = $logging->getRecordFactory(); try { // Switch to a factory that uses PSR3-interpolation. $logging->setRecordFactory(static::$factory); $logging->{$level}($message, $context); } catch (Exception $e) { } // Restore the original factory. $logging->setRecordFactory($factory); }
/** * Called after the bot has finished its execution * to perform cleanup tasks. * * \param resource $handle * Open file handle on the pidfile. * * \param string $pidfile * Name of the pidfile. * * \return * This method does not return anything. */ public static function cleanupPidfile($handle, $pidfile) { flock($handle, LOCK_UN); @unlink($pidfile); $logger = \Plop\Plop::getInstance(); $logger->debug('Removed lock on pidfile (%(pidfile)s)', array('pidfile' => $pidfile)); }
/** * Create the base for a proxy client. * * \param stream $socket * A socket which will be used to tunnel data. */ public function __construct($socket) { if (!is_resource($socket)) { throw new \Erebot\InvalidValueException('Not a socket'); } $this->socket = $socket; $logging = \Plop\Plop::getInstance(); $this->logger = $logging->getLogger(__FILE__ . DIRECTORY_SEPARATOR); }
/** * @dataProvider vectors * @group medium */ public function testED25519($index, $private, $public, $message, $signature) { $logging = \Plop\Plop::getInstance(); $logging->debug("Testing using vector #%d", array($index)); $msg = pack('H*', $message); $key = new ED25519TestHelper(pack('H*', $public), pack('H*', $private)); $result = $key->sign($msg); $this->assertSame($signature, bin2hex($result)); $this->assertTrue($key->check($msg, $result)); }
/** * Construct a new instance of this filter. * * \param int|string $level * Name or value of the minimal level that this filter * should allow. */ public function __construct($level) { if (is_string($level)) { $plop = \Plop\Plop::getInstance(); $level = \Plop\Plop::getLevelValue($level); } if (!is_int($level)) { throw new \Plop\Exception('Invalid value'); } $this->level = $level; }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { unset($context['rekeying']); $response = new \fpoirotte\Pssht\Messages\NEWKEYS(); $transport->writeMessage($response); $logging = \Plop\Plop::getInstance(); // Reset the various keys. $kexAlgo = $context['kexAlgo']; $kexAlgo = new $kexAlgo(); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeMpint($context['DH']->getSharedSecret()); $sharedSecret = $encoder->getBuffer()->get(0); $exchangeHash = $context['DH']->getExchangeHash(); $sessionId = $context['sessionIdentifier']; $limiters = array('A' => array($context['C2S']['Encryption'], 'getIVSize'), 'B' => array($context['S2C']['Encryption'], 'getIVSize'), 'C' => array($context['C2S']['Encryption'], 'getKeySize'), 'D' => array($context['S2C']['Encryption'], 'getKeySize'), 'E' => array($context['C2S']['MAC'], 'getKeySize'), 'F' => array($context['C2S']['MAC'], 'getKeySize')); $shared = gmp_strval($context['DH']->getSharedSecret(), 16); $shared = str_pad($shared, strlen($shared) + 1 >> 1 << 1, '0', STR_PAD_LEFT); $logging->debug('Key exchange: %s', array($context['kexAlgo'])); $logging->debug('Shared secret: %s', array(wordwrap($shared, 16, ' ', true))); $logging->debug('Hash: %s', array(wordwrap(bin2hex($exchangeHash), 16, ' ', true))); foreach (array('A', 'B', 'C', 'D', 'E', 'F') as $keyIndex) { $key = $kexAlgo->hash($sharedSecret . $exchangeHash . $keyIndex . $sessionId); $limit = call_user_func($limiters[$keyIndex]); while (strlen($key) < $limit) { $key .= $kexAlgo->hash($sharedSecret . $exchangeHash . $key); } $key = (string) substr($key, 0, $limit); $context['keys'][$keyIndex] = $key; $logging->debug('Key %(keyName)s: %(keyValue)s', array('keyName' => $keyIndex, 'keyValue' => wordwrap(bin2hex($key), 16, ' ', true))); } // Encryption $cls = $context['C2S']['Encryption']; $transport->setDecryptor(new $cls($context['keys']['A'], $context['keys']['C'])); $logging->debug('C2S Encryption: %s', array($cls)); $cls = $context['S2C']['Encryption']; $transport->setEncryptor(new $cls($context['keys']['B'], $context['keys']['D'])); $logging->debug('S2C Encryption: %s', array($cls)); // MAC $cls = $context['C2S']['MAC']; $transport->setInputMAC(new $cls($context['keys']['E'])); $logging->debug('C2S MAC: %s', array($cls)); $cls = $context['S2C']['MAC']; $transport->setOutputMAC(new $cls($context['keys']['F'])); $logging->debug('S2C MAC: %s', array($cls)); // Compression $cls = $context['C2S']['Compression']; $transport->setUncompressor(new $cls(CompressionInterface::MODE_UNCOMPRESS)); $logging->debug('C2S Compression: %s', array($cls)); $cls = $context['S2C']['Compression']; $transport->setCompressor(new $cls(CompressionInterface::MODE_COMPRESS)); $logging->debug('S2C Compression: %s', array($cls)); return true; }
public function authenticate(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base $message, \fpoirotte\Pssht\Transport $transport, array &$context) { if (!$message instanceof \fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Password) { throw new \InvalidArgumentException(); } $logging = \Plop\Plop::getInstance(); $reverse = gethostbyaddr($transport->getAddress()); if (isset($this->credentials[$message->getUserName()]) && $message->getPassword() === $this->credentials[$message->getUserName()]) { $logging->info('Accepted password connection from ' . 'remote host "%(reverse)s" to "%(luser)s"', array('reverse' => $reverse, 'luser' => escape($message->getUserName()))); return self::AUTH_ACCEPT; } $logging->info('Rejected password connection from ' . 'remote host "%(reverse)s" to "%(luser)s" ' . '(invalid credentials)', array('reverse' => $reverse, 'luser' => escape($message->getUserName()))); return self::AUTH_REJECT; }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $localChannel = $decoder->decodeUint32(); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeUint32($localChannel); $decoder->getBuffer()->unget($encoder->getBuffer()->get(0)); if (isset($this->handlers[$localChannel][$msgType])) { $handler = $this->handlers[$localChannel][$msgType]; $logging = \Plop\Plop::getInstance(); $logging->debug('Calling %(handler)s for channel #%(channel)d ' . 'with message type #%(msgType)d', array('handler' => get_class($handler) . '::handle', 'channel' => $localChannel, 'msgType' => $msgType)); return $handler->handle($msgType, $decoder, $transport, $context); } return true; }
public function authenticate(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base $message, \fpoirotte\Pssht\Transport $transport, array &$context) { if (!$message instanceof \fpoirotte\Pssht\Messages\USERAUTH\REQUEST\HostBased) { throw new \InvalidArgumentException(); } $logging = \Plop\Plop::getInstance(); $reverse = gethostbyaddr($transport->getAddress()); $untrustedHost = rtrim($message->getHostname(), '.'); $algos = \fpoirotte\Pssht\Algorithms::factory(); $cls = $algos->getClass('PublicKey', $message->getAlgorithm()); if ($cls === null || !$this->store->exists($message->getUserName(), $message->getKey())) { $logging->info('Rejected host based connection from %(ruser)s@%(rhost)s ' . '(%(ruser)s@%(reverse)s) to "%(luser)s" ' . '(unsupported key)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse)); return self::AUTH_REMOVE; } $key = $cls::loadPublic(base64_encode($message->getKey())); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeString($context['DH']->getExchangeHash()); $encoder->encodeBytes(chr(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base::getMessageId())); $encoder->encodeString($message->getUserName()); $encoder->encodeString($message->getServiceName()); $encoder->encodeString(static::getName()); $encoder->encodeString($message->getAlgorithm()); $encoder->encodeString($message->getKey()); $encoder->encodeString($message->getHostname()); $encoder->encodeString($message->getRemoteUser()); if (!$key->check($encoder->getBuffer()->get(0), $message->getSignature())) { $logging->warn('Rejected host based connection from %(ruser)s@%(rhost)s ' . '(%(ruser)s@%(reverse)s) to "%(luser)s" (invalid signature)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse)); return self::AUTH_REJECT; } if ($reverse !== $untrustedHost) { $logging->warning('Ignored reverse lookup mismatch for %(address)s (' . '"%(reverse)s" vs. "%(untrusted)s")', array('address' => $transport->getAddress(), 'reverse' => $reverse, 'untrusted' => escape($untrustedHost))); } if ($message->getUserName() !== $message->getRemoteUser()) { $logging->warning('Rejected host based connection from %(ruser)s@%(rhost)s ' . '(%(ruser)s@%(reverse)s): remote user does not match ' . 'local user (%(luser)s)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse)); return self::AUTH_REMOVE; } $logging->info('Accepted host based connection ' . 'from "%(ruser)s@%(rhost)s" (%(ruser)s@%(reverse)s) ' . 'to "%(luser)s" (using "%(algorithm)s" algorithm)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse, 'algorithm' => escape($message->getAlgorithm()))); return self::AUTH_ACCEPT; }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $hostAlgo = null; foreach ($context['kex']['client']->getServerHostKeyAlgos() as $algo) { if (isset($context['serverKeys'][$algo])) { $hostAlgo = $algo; break; } } if ($hostAlgo === null) { throw new \RuntimeException(); } $response = $this->createResponse($decoder, $transport, $context, $hostAlgo); $logging = \Plop\Plop::getInstance(); $secret = gmp_strval($response->getSharedSecret(), 16); $logging->debug("Shared secret:\r\n%s", array(wordwrap($secret, 16, ' ', true))); $logging->debug('Hash: %s', array(wordwrap(bin2hex($response->getExchangeHash()), 16, ' ', true))); if (!isset($context['sessionIdentifier'])) { $context['sessionIdentifier'] = $response->getExchangeHash(); } $context['DH'] = $response; $transport->writeMessage($response); return true; }
/** * Check for valid algorithm names. * * \param string $class * Name of the class whose algorithm name must be checked. * * \retval string * The class' algorithm name. * * \retval null * No valid algorithm name for the given class. */ protected function getValidAlgorithm($class) { $logging = \Plop\Plop::getInstance(); $w = 'abcdefghijklmnopqrstuvwxyz' . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . '1234567890-@.'; $name = $class::getName(); if (!is_string($name) || strspn($name, $w) !== strlen($name)) { $logging->debug('Skipping algorithm "%(name)s" (invalid algorithm name)', array('name' => $name)); return null; } return $name; }
/** * Construct a new SSH_MSG_KEXDH_REPLY message. * * \param fpoirotte::Pssht::ECC::Curve $curve * Elliptic curve in use. * * \param fpoirotte::Pssht::Messages::KEX::ECDH::INIT::RFC5656 $kexDHInit * Client's contribution to the Diffie-Hellman Key Exchange. * * \param fpoirotte::Pssht::PublicKeyInterface $key * Server's public key. * * \param fpoirotte::Pssht::EncryptionInterface $encryptionAlgo * Encryption algorithm in use. * * \param fpoirotte::Pssht::KEXInterface $kexAlgo * Key exchange algorithm to use. * * \param fpoirotte::Pssht::Messages::KEXINIT $serverKEX * Algorithms supported by the server. * * \param fpoirotte::Pssht::Messages::KEXINIT $clientKEX * Algorithms supported by the client. * * \param string $serverIdent * Server's identification string * * \param string $clientIdent * Client's identification string */ public function __construct(\fpoirotte\Pssht\ECC\Curve $curve, \fpoirotte\Pssht\Messages\KEX\ECDH\INIT\RFC5656 $kexDHInit, \fpoirotte\Pssht\PublicKeyInterface $key, \fpoirotte\Pssht\EncryptionInterface $encryptionAlgo, \fpoirotte\Pssht\KEXInterface $kexAlgo, \fpoirotte\Pssht\Messages\KEXINIT $serverKEX, \fpoirotte\Pssht\Messages\KEXINIT $clientKEX, $serverIdent, $clientIdent) { if (!is_string($serverIdent)) { throw new \InvalidArgumentException(); } if (!is_string($clientIdent)) { throw new \InvalidArgumentException(); } $len = strlen(gmp_strval($curve->getOrder(), 2)); $len = ceil($len / 8); $randBytes = openssl_random_pseudo_bytes($len); $d_S = gmp_mod(gmp_init(bin2hex($randBytes), 16), $curve->getModulus()); $this->Q_S = $curve->getGenerator()->multiply($curve, $d_S); $Q_C = $kexDHInit->getQ(); /// @FIXME this is not optimal... $algorithms = \fpoirotte\Pssht\Algorithms::factory(); $cls = $algorithms->getClass('PublicKey', 'ecdsa-sha2-' . $curve->getName()); $clientPK = new $cls($Q_C); if (!$clientPK->isValid()) { throw new \InvalidArgumentException(); } // EC Co-factor DH (sec1-v2, section 3.3.2). $P = $Q_C->multiply($curve, gmp_mul($curve->getCofactor(), $d_S)); if ($P->isIdentity($curve)) { throw new \InvalidArgumentException(); } $this->K = $P->x; $this->curve = $curve; $this->K_S = $key; $this->kexDHInit = $kexDHInit; $this->kexAlgo = $kexAlgo; $this->serverKEX = $serverKEX; $this->clientKEX = $clientKEX; $this->serverIdent = $serverIdent; $this->clientIdent = $clientIdent; $msgId = chr(\fpoirotte\Pssht\Messages\KEXINIT::getMessageId()); // $sub is used to create the structure for the hashing function. $sub = new \fpoirotte\Pssht\Wire\Encoder(new \fpoirotte\Pssht\Buffer()); $this->K_S->serialize($sub); $K_S = $sub->getBuffer()->get(0); $sub->encodeString($this->clientIdent); $sub->encodeString($this->serverIdent); // $sub2 is used to compute the value // of various fields inside the structure. $sub2 = new \fpoirotte\Pssht\Wire\Encoder(new \fpoirotte\Pssht\Buffer()); $sub2->encodeBytes($msgId); // Add message identifier. $this->clientKEX->serialize($sub2); $sub->encodeString($sub2->getBuffer()->get(0)); $sub2->encodeBytes($msgId); // Add message identifier. $this->serverKEX->serialize($sub2); $sub->encodeString($sub2->getBuffer()->get(0)); $sub->encodeString($K_S); $sub->encodeString($Q_C->serialize($curve)); $sub->encodeString($this->Q_S->serialize($curve)); $sub->encodeMpint($this->K); $logging = \Plop\Plop::getInstance(); $origData = $sub->getBuffer()->get(0); $data = wordwrap(bin2hex($origData), 4, ' ', true); $data = wordwrap($data, 32 + 7, PHP_EOL, true); $logging->debug("Signature payload:\r\n%s", array($data)); $this->H = $this->kexAlgo->hash($origData); }
/** * Construct a new SSH_MSG_KEXDH_REPLY message. * * \param fpoirotte::Pssht::Messages::KEXDH::INIT $kexDHInit * Client's contribution to the Diffie-Hellman Key Exchange. * * \param fpoirotte::Pssht::PublicKeyInterface $key * Server's public key. * * \param fpoirotte::Pssht::EncryptionInterface $encryptionAlgo * Encryption algorithm in use. * * \param fpoirotte::Pssht::KEXInterface $kexAlgo * Key exchange algorithm to use. * * \param fpoirotte::Pssht::Messages::KEXINIT $serverKEX * Algorithms supported by the server. * * \param fpoirotte::Pssht::Messages::KEXINIT $clientKEX * Algorithms supported by the client. * * \param string $serverIdent * Server's identification string * * \param string $clientIdent * Client's identification string */ public function __construct(\fpoirotte\Pssht\Messages\KEXDH\INIT $kexDHInit, \fpoirotte\Pssht\PublicKeyInterface $key, \fpoirotte\Pssht\EncryptionInterface $encryptionAlgo, \fpoirotte\Pssht\KEXInterface $kexAlgo, \fpoirotte\Pssht\Messages\KEXINIT $serverKEX, \fpoirotte\Pssht\Messages\KEXINIT $clientKEX, $serverIdent, $clientIdent) { if (!is_string($serverIdent)) { throw new \InvalidArgumentException(); } if (!is_string($clientIdent)) { throw new \InvalidArgumentException(); } $keyLength = min(20, max($encryptionAlgo->getKeySize(), 16)); $randBytes = openssl_random_pseudo_bytes(2 * $keyLength); $y = gmp_init(bin2hex($randBytes), 16); $prime = gmp_init($kexAlgo::getPrime(), 16); $this->f = gmp_powm($kexAlgo::getGenerator(), $y, $prime); $this->K = gmp_powm($kexDHInit->getE(), $y, $prime); $this->K_S = $key; $this->kexDHInit = $kexDHInit; $this->kexAlgo = $kexAlgo; $this->serverKEX = $serverKEX; $this->clientKEX = $clientKEX; $this->serverIdent = $serverIdent; $this->clientIdent = $clientIdent; $msgId = chr(\fpoirotte\Pssht\Messages\KEXINIT::getMessageId()); // $sub is used to create the structure for the hashing function. $sub = new \fpoirotte\Pssht\Wire\Encoder(new \fpoirotte\Pssht\Buffer()); $this->K_S->serialize($sub); $K_S = $sub->getBuffer()->get(0); $sub->encodeString($this->clientIdent); $sub->encodeString($this->serverIdent); // $sub2 is used to compute the value // of various fields inside the structure. $sub2 = new \fpoirotte\Pssht\Wire\Encoder(new \fpoirotte\Pssht\Buffer()); $sub2->encodeBytes($msgId); // Add message identifier. $this->clientKEX->serialize($sub2); $sub->encodeString($sub2->getBuffer()->get(0)); $sub2->encodeBytes($msgId); // Add message identifier. $this->serverKEX->serialize($sub2); $sub->encodeString($sub2->getBuffer()->get(0)); $sub->encodeString($K_S); $sub->encodeMpint($this->kexDHInit->getE()); $sub->encodeMpint($this->f); $sub->encodeMpint($this->K); $logging = \Plop\Plop::getInstance(); $origData = $sub->getBuffer()->get(0); $data = wordwrap(bin2hex($origData), 4, ' ', true); $data = wordwrap($data, 32 + 7, PHP_EOL, true); $logging->debug("Signature payload:\r\n%s", array($data)); $this->H = $this->kexAlgo->hash($origData); }
public function load($configData, $source) { $logger = \Plop\Plop::getInstance(); $possibleSources = array(self::LOAD_FROM_FILE, self::LOAD_FROM_STRING); if (!in_array($source, $possibleSources, true)) { throw new \Erebot\InvalidValueException('Invalid $source'); } if ($source == self::LOAD_FROM_FILE) { if (is_string($configData) && $configData != "") { if (!strncasecmp(PHP_OS, "Win", 3)) { if (!in_array($configData[0], array("/", "\\")) && strlen($configData) > 1 && $configData[1] != ":") { $configData = getcwd() . DIRECTORY_SEPARATOR . $configData; } } elseif ($configData[0] != DIRECTORY_SEPARATOR) { $configData = getcwd() . DIRECTORY_SEPARATOR . $configData; } $file = \Erebot\URI::fromAbsPath($configData, false); } elseif (is_object($configData) && $configData instanceof \Erebot\URIInterface) { $file = $configData; } else { throw new \Erebot\InvalidValueException("Invalid configuration file"); } } elseif (!is_string($configData)) { throw new \Erebot\InvalidValueException("Invalid configuration file"); } else { $file = null; } $mainSchema = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'config.rng'; $mainSchema = file_get_contents($mainSchema); $ue = libxml_use_internal_errors(true); $domxml = new \Erebot\DOM(); if ($source == self::LOAD_FROM_FILE) { $domxml->load((string) $file); } else { $domxml->loadXML($configData); } $domxml->xinclude(LIBXML_NOBASEFIX); $this->stripXGlobWrappers($domxml); $ok = $domxml->relaxNGValidateSource($mainSchema); $errors = $domxml->getErrors(); libxml_use_internal_errors($ue); if (!$ok || count($errors)) { // Some unpredicted error occurred, // show some (hopefully) useful information. $logger->error(print_r($errors, true)); throw new \Erebot\InvalidValueException('Errors were found while validating the configuration file'); } $xml = simplexml_import_dom($domxml); parent::__construct($this, $xml); if (!isset($xml['version'])) { $this->version = null; } else { $this->version = (string) $xml['version']; } if (!isset($xml['timezone'])) { throw new \Erebot\InvalidValueException('No timezone defined'); } $this->timezone = (string) $xml['timezone']; // Set timezone information. // This is needed to configure the logging subsystem. if (function_exists('date_default_timezone_set')) { if (!date_default_timezone_set($this->timezone)) { throw \Erebot\InvalidValueException(sprintf('Invalid timezone: "%s"', $this->timezone)); } } $daemonize = isset($xml['daemon']) ? $this->parseBool((string) $xml['daemon']) : false; $userIdentity = isset($xml['uid']) ? (string) $xml['uid'] : null; $groupIdentity = isset($xml['gid']) ? (string) $xml['gid'] : null; $pidfile = isset($xml['pidfile']) ? (string) $xml['pidfile'] : null; if ($daemonize === null) { throw new \Erebot\InvalidValueException('Invalid "daemon" value'); } if (!isset($xml['commands-prefix'])) { $this->commandsPrefix = '!'; } else { $this->commandsPrefix = (string) $xml['commands-prefix']; if (strcspn($this->commandsPrefix, " \r\n\t") != strlen($this->commandsPrefix)) { throw new \Erebot\InvalidValueException('Invalid command prefix'); } } $logger->debug($this->coreTranslator->gettext('Loaded configuration data')); $this->networks = array(); foreach ($xml->networks->network as $netCfg) { /// @TODO use dependency injection instead. $newConfig = new \Erebot\Config\Network($this, $netCfg); $this->networks[$newConfig->getName()] = $newConfig; unset($newConfig); } if ($source == self::LOAD_FROM_FILE) { $this->configFile = $configData; } else { $this->configFile = null; } // Default values. $this->daemonize = $daemonize; $this->userIdentity = $userIdentity; $this->groupIdentity = $groupIdentity; $this->pidfile = $pidfile; }
/** * Constructor for modules. * * \param string|null $channel * (optional) The channel this instance applies to. * This will be \b null for modules loaded at the server * level or higher in the configuration hierarchy. */ public final function __construct($channel) { $this->connection = $this->translator = $this->mainCfg = null; $this->channel = $channel; $this->factories = array(); $ifaces = array('!EventHandler' => '\\Erebot\\EventHandler', '!Identity' => '\\Erebot\\Identity', '!NumericHandler' => '\\Erebot\\NumericHandler', '!NumericReference' => '\\Erebot\\NumericReference', '!Styling' => '\\Erebot\\Styling', '\\Erebot\\Styling\\Variables\\CurrencyInterface' => '\\Erebot\\Styling\\Variables\\CurrencyVariable', '\\Erebot\\Styling\\Variables\\DateTimeInterface' => '\\Erebot\\Styling\\Variables\\DateTimeVariable', '\\Erebot\\Styling\\Variables\\DurationInterface' => '\\Erebot\\Styling\\Variables\\DurationVariable', '!TextWrapper' => '\\Erebot\\TextWrapper', '!Timer' => '\\Erebot\\Timer'); foreach ($ifaces as $iface => $cls) { try { $this->setFactory($iface, $cls); } catch (\Erebot\InvalidValueException $e) { // Ignore silently as the only time the default classes // won't exist is when we run the tests for some module. } } /// @FIXME: handle dependency injection somehow $this->logger = null; if (class_exists('\\Plop\\Plop')) { $this->logger =& \Plop\Plop::getInstance(); } }
/** * Constructs the UNIX socket that represents the prompt. * * \param Erebot::Interfaces::Core $bot * Instance of the bot to operate on. * * \param string $connector * (optional) Path where the newly-created UNIX socket * will be made accessible. The default is to create * a UNIX socket named "Erebot.sock" in the system's * temporary directory (usually "/tmp/"). * * \param mixed $group * (optional) Either the name or the identifier * of the UNIX group the socket will belong to. * The default is to not change the group of the * socket (i.e. to keep whatever is the main group * for the user running Erebot). * * \param int $perms * (optional) UNIX permissions the newly-created * socket will receive. The default is to give * read/write access to the user running Erebot and * to the group the socket belongs to (see $group). * * \note * On most systems, only the superuser may change * the group of a file arbitrarily, while other * users may only change it to a group they are * a member of. * * \warning * Using the wrong combination of $group and $perms * may lead to security issues. Use with caution. * The default values are safe as long as you trust * users belonging to the same group as the main * group of the user running Erebot. * * \see * http://php.net/chmod provides more information * on the meaning of $perms' value * (see the description for \c mode). */ public function __construct(\Erebot\Interfaces\Core $bot, $connector = null, $group = null, $perms = 0660) { $this->bot = $bot; if ($connector === null) { $connector = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'Erebot.sock'; } $this->socket = stream_socket_server("udg://" . $connector, $errno, $errstr, STREAM_SERVER_BIND); if (!$this->socket) { throw new \Exception("Could not create prompt (" . $errstr . ")"); } // Cleanup on shutdown. register_shutdown_function(function ($socket) { @unlink($socket); }, $connector); // Change group. if ($group !== null) { if (!@chgrp($connector, $group)) { throw new \Exception("Could not change group to '{$group}' for '{$connector}'"); } } // Change permissions. if (!chmod($connector, $perms)) { throw new \Exception("Could not set permissions to {$perms} on '{$connector}'"); } // Flush any received data on the socket, because // any data sent before the group and permissions // were set may have come from an untrusted source. $flush = array($this->socket); $dummy = null; while (stream_select($flush, $dummy, $dummy, 0) == 1) { if (fread($this->socket, 8192) === false) { throw new \Exception("Error while flushing the socket"); } } $this->io = new \Erebot\LineIO(\Erebot\LineIO::EOL_ANY, $this->socket); $logger = \Plop\Plop::getInstance(); $logger->info($bot->gettext('Prompt started in "%(path)s"'), array('path' => $connector)); }
/** * Try to read and handle a single SSH message. * * \retval bool * \b true if a message was successfully read and handled, * \b false otherwise. * * \note * Depending on the circumstances, messages may be successfully * read but left unhandled (eg. because the message was incomplete). * In such cases, the message will be reinjected and \b false * returned, making it possible for a future call to this method * to handle the (full) message again. */ public function readMessage() { $logging = \Plop\Plop::getInstance(); // Initial state: expect the client's identification string. if (!isset($this->context['identity']['client'])) { return $this->handlers[256]->handle(null, $this->decoder, $this, $this->context); } $blockSize = max($this->decryptor->getBlockSize(), 8); // See http://api.libssh.org/rfc/PROTOCOL // for more information on EtM (Encrypt-then-MAC). if ($this->inMAC instanceof \fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) { $encPayload = $this->decoder->getBuffer()->get(4); if ($encPayload === null) { return false; } $unencrypted = $encPayload; } elseif ($this->decryptor instanceof \fpoirotte\Pssht\AEADInterface) { $encPayload = $this->decoder->getBuffer()->get(4); if ($encPayload === null) { return false; } $unencrypted = $this->decryptor->decrypt($this->inSeqNo, $encPayload); $this->decoder->getBuffer()->unget($encPayload); $encPayload = ''; } else { $encPayload = $this->decoder->getBuffer()->get($blockSize); if ($encPayload === null) { return false; } $unencrypted = $this->decryptor->decrypt($this->inSeqNo, $encPayload); } $buffer = new \fpoirotte\Pssht\Buffer($unencrypted); $decoder = new \fpoirotte\Pssht\Wire\Decoder($buffer); $packetLength = $decoder->decodeUint32(); // Read the rest of the message. if ($this->inMAC instanceof \fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) { // Only the main payload remains. $toRead = $packetLength; } elseif ($this->decryptor instanceof \fpoirotte\Pssht\AEADInterface) { // packet length (authenticated data) // + encrypted payload // + authentication tag (AT) $toRead = 4 + $packetLength + $this->decryptor->getSize(); } else { $toRead = 4 - $blockSize + $packetLength; } if ($toRead < 0) { throw new \RuntimeException(); } $unencrypted2 = ''; if ($toRead !== 0) { $encPayload2 = $this->decoder->getBuffer()->get($toRead); if ($encPayload2 === null) { $this->decoder->getBuffer()->unget($encPayload); return false; } $unencrypted2 = $this->decryptor->decrypt($this->inSeqNo, $encPayload2); if ($unencrypted2 === null) { return false; } $buffer->push($unencrypted2); } $paddingLength = ord($decoder->decodeBytes()); $payload = $decoder->decodeBytes($packetLength - $paddingLength - 1); $padding = $decoder->decodeBytes($paddingLength); // If a MAC is in use. $macSize = $this->inMAC->getSize(); $actualMAC = ''; if ($macSize > 0) { $actualMAC = $this->decoder->getBuffer()->get($macSize); if ($actualMAC === null) { $this->decoder->getBuffer()->unget($encPayload2)->unget($encPayload); return false; } if ($this->inMAC instanceof \fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) { // $encPayload actually contains packet length (in plaintext). $macData = $encPayload . $encPayload2; } else { $macData = $unencrypted . $unencrypted2; } $expectedMAC = $this->inMAC->compute($this->inSeqNo, (string) substr($macData, 0, $packetLength + 4)); if ($expectedMAC !== $actualMAC) { throw new \RuntimeException(); } } if (!isset($packetLength, $paddingLength, $payload, $padding, $actualMAC)) { $this->decoder->getBuffer()->unget($actualMAC)->unget($encPayload2)->unget($encPayload); $logging->error('Something went wrong during decoding'); return false; } $payload = $this->uncompressor->update($payload); $decoder = new \fpoirotte\Pssht\Wire\Decoder(new \fpoirotte\Pssht\Buffer($payload)); $msgType = ord($decoder->decodeBytes(1)); $logging->debug('Received payload: %s', array(\escape($payload))); $res = true; if (isset($this->handlers[$msgType])) { $handler = $this->handlers[$msgType]; $logging->debug('Calling %(handler)s with message type #%(msgType)d', array('handler' => get_class($handler) . '::handle', 'msgType' => $msgType)); try { $res = $handler->handle($msgType, $decoder, $this, $this->context); } catch (\fpoirotte\Pssht\Messages\DISCONNECT $e) { if ($e->getCode() !== 0) { $this->writeMessage($e); } throw $e; } } else { $logging->warn('Unimplemented message type (%d)', array($msgType)); $response = new \fpoirotte\Pssht\Messages\UNIMPLEMENTED($this->inSeqNo); $this->writeMessage($response); } $this->inSeqNo = ++$this->inSeqNo & 0xffffffff; return $res; }
public function isEnabledFor($level) { if (!is_int($level)) { $plop = \Plop\Plop::getInstance(); $level = $plop->getLevelValue($level); } return $level >= $this->level; }
/** * Creates the connections for this instance. * * \param Erebot::Interfaces::ConnectionFactory $factory * The factory to use to create the connections. * * \param Erebot::Interfaces::Config::Main $config * The main configuration for the bot. */ protected function createConnections(\Erebot\Interfaces\ConnectionFactory $factory, \Erebot\Interfaces\Config\Main $config) { $logger = \Plop\Plop::getInstance(); // List existing connections so they // can eventually be reused. $newConnections = $currentConnections = array(); foreach ($this->connections as $connection) { $connCfg = $connection->getConfig(null); if ($connCfg) { $netCfg = $connCfg->getNetworkCfg(); $currentConnections[$netCfg->getName()] = $connection; } else { $newConnections[] = $connection; } } // Let's establish some contacts. $networks = $config->getNetworks(); foreach ($networks as $network) { $netName = $network->getName(); if (isset($currentConnections[$netName])) { try { $uris = $currentConnections[$netName]->getConfig(null)->getConnectionURI(); $uri = new \Erebot\URI($uris[count($uris) - 1]); $serverCfg = $network->getServerCfg((string) $uri); $logger->info($this->gettext('Reusing existing connection ' . 'for network "%(network)s"'), array('network' => $netName)); // Move it from existing connections to new connections, // marking it as still being in use. $copy = clone $currentConnections[$netName]; $copy->reload($serverCfg); $newConnections[] = $copy; unset($currentConnections[$netName]); continue; } catch (\Erebot\NotFoundException $e) { // Nothing to do. } } if (!in_array('\\Erebot\\Module\\AutoConnect', $network->getModules(true))) { continue; } $servers = $network->getServers(); foreach ($servers as $server) { $uris = $server->getConnectionURI(); $serverUri = new \Erebot\URI($uris[count($uris) - 1]); try { $connection = $factory->newConnection($this, $server); // Drop connection to a (now-)unconfigured // server on that network. if (isset($currentConnections[$netName])) { $currentConnections[$netName]->disconnect(); unset($currentConnections[$netName]); } $logger->info($this->gettext('Trying to connect to "%(uri)s"...'), array('uri' => $serverUri)); $connection->connect(); $newConnections[] = $connection; $logger->info($this->gettext('Successfully connected to "%(uri)s"...'), array('uri' => $serverUri)); break; } catch (\Erebot\ConnectionFailureException $e) { // Nothing to do... We simply // try the next server on the // list until we successfully // connect or cycle the list. $logger->exception($this->gettext('Could not connect to "%(uri)s"'), $e, array('uri' => $serverUri)); } } } // Gracefully quit leftover connections. foreach ($currentConnections as $connection) { $connection->disconnect(); } $this->connections = $newConnections; $this->mainCfg = $config; }
/** * Run this CLI script. * * \param array $args * A list of arguments passed to this script. * * \retval int * Exit code. \c 0 is used to indicate a * success, while any other code indicates * an error. * * \note * In case of an error, additional messages * may be sent to \c STDERR by this script. */ public function run(array $args) { $prog = array_shift($args); try { list($options, $params) = $this->parse($args); } catch (\Exception $e) { fprintf(STDERR, '%s: %s' . PHP_EOL, $prog, $e->getMessage()); return 2; } // Show help. if ($options['h']) { $this->printUsage(new \fpoirotte\XRL\Output(STDOUT), $prog); return 0; } // Show version. if ($options['V']) { $version = self::getVersion(); $license = self::getCopyrightAndLicense(); echo 'XRL version ' . $version . PHP_EOL; echo PHP_EOL . $license . PHP_EOL; echo 'Visit https://github.com/fpoirotte/XRL for more!' . PHP_EOL; return 0; } // Do we have enough arguments to do something? if ($params['serverURL'] === null || $params['procedure'] === null) { $this->printUsage(new \fpoirotte\XRL\Output(STDERR), $prog); return 2; } // Then let's do it! $encoder = new \fpoirotte\XRL\NativeEncoder(new \fpoirotte\XRL\Encoder($options['t'], true)); $decoder = new \fpoirotte\XRL\NativeDecoder(new \fpoirotte\XRL\Decoder($options['t'], $options['x'])); $request = new \fpoirotte\XRL\Request($params['procedure'], $params['additional']); // Change verbosity as necessary. if (class_exists('\\Plop\\Plop')) { $logging = \Plop\Plop::getInstance(); $logging->getLogger()->setLevel(40 - max(4, $options['v']) * 10); } else { $logging = null; } // Prepare the request. $xml = $encoder->encodeRequest($request); $logging and $logging->debug("Request:\n%(request)s", array('request' => $xml)); if ($options['n']) { echo 'Not sending the actual query due to dry run mode.' . PHP_EOL; return 0; } // Prepare the context. $ctxOptions = array('http' => array('method' => 'POST', 'content' => $xml, 'header' => 'Content-Type: text/xml')); $context = stream_context_create($ctxOptions); libxml_set_streams_context($context); // Send the request and process the response. try { $result = $decoder->decodeResponse($params['serverURL']); } catch (\Exception $result) { // Nothing to do. } echo 'Result:' . PHP_EOL . print_r($result, true) . PHP_EOL; return 0; }
/** * Writes a single line from the output buffer * to the socket. * * \retval int * The number of bytes successfully * written on the socket. * * \retval false * The connection was lost while trying * to send the line or the output buffer * was empty. */ public function write() { if (!count($this->sndQueue)) { return false; } $line = array_shift($this->sndQueue); $logger = \Plop\Plop::getInstance(); // Make sure we send the whole line, // with a trailing CR LF sequence. $eol = $this->eol[count($this->eol) - 1]; $line .= $eol; $len = strlen($line); for ($written = 0; $written < $len; $written += $fwrite) { $fwrite = @fwrite($this->socket, substr($line, $written)); if ($fwrite === false) { return false; } } $line = substr($line, 0, -strlen($eol)); $logger->debug('%(line)s', array('line' => addcslashes($line, ".."))); return $written; }
<?php require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; $logging = \Plop\Plop::getInstance(); $handlers = $logging->getLogger()->getHandlers(); $handlers[0] = new \Plop\Handler\Stream(fopen('/dev/null', 'w'));