function _connect()
{
if ($this->bitmap & self::MASK_CONSTRUCTOR) {
return false;
}
$this->bitmap |= self::MASK_CONSTRUCTOR;
$this->curTimeout = $this->timeout;
$this->last_packet = microtime(true);
if (!is_resource($this->fsock)) {
$start = microtime(true);
// with stream_select a timeout of 0 means that no timeout takes place;
// with fsockopen a timeout of 0 means that you instantly timeout
// to resolve this incompatibility a timeout of 100,000 will be used for fsockopen if timeout is 0
$this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout);
if (!$this->fsock) {
$host = $this->host . ':' . $this->port;
throw new \RuntimeException(rtrim("Cannot connect to {$host}. Error {$errno}. {$errstr}"));
}
$elapsed = microtime(true) - $start;
$this->curTimeout -= $elapsed;
if ($this->curTimeout <= 0) {
$this->is_timeout = true;
return false;
}
}
$this->identifier = $this->_generate_identifier();
fputs($this->fsock, $this->identifier . "\r\n");
/* According to the SSH2 specs,
"The server MAY send other lines of data before sending the version
string. Each line SHOULD be terminated by a Carriage Return and Line
Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded
in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients
MUST be able to process such lines." */
$data = '';
while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\\d\\.\\d+).*)#ms', $data, $matches)) {
$line = '';
while (true) {
if ($this->curTimeout) {
if ($this->curTimeout < 0) {
$this->is_timeout = true;
return false;
}
$read = array($this->fsock);
$write = $except = null;
$start = microtime(true);
$sec = floor($this->curTimeout);
$usec = 1000000 * ($this->curTimeout - $sec);
// on windows this returns a "Warning: Invalid CRT parameters detected" error
// the !count() is done as a workaround for <https://bugs.php.net/42682>
if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
$this->is_timeout = true;
return false;
}
$elapsed = microtime(true) - $start;
$this->curTimeout -= $elapsed;
}
$temp = stream_get_line($this->fsock, 255, "\n");
if (strlen($temp) == 255) {
continue;
}
$line .= "{$temp}\n";
// quoting RFC4253, "Implementers who wish to maintain
// compatibility with older, undocumented versions of this protocol may
// want to process the identification string without expecting the
// presence of the carriage return character for reasons described in
// Section 5 of this document."
//if (substr($line, -2) == "\r\n") {
// break;
//}
break;
}
$data .= $line;
}
if (feof($this->fsock)) {
throw new \RuntimeException('Connection closed by server');
}
$extra = $matches[1];
if (defined('NET_SSH2_LOGGING')) {
$this->_append_log('<-', $matches[0]);
$this->_append_log('->', $this->identifier . "\r\n");
}
$this->server_identifier = trim($temp, "\r\n");
if (strlen($extra)) {
$this->errors[] = utf8_decode($data);
}
if ($matches[3] != '1.99' && $matches[3] != '2.0') {
throw new \RuntimeException("Cannot connect to SSH {$matches['1']} servers");
}
$response = $this->_get_binary_packet();
if ($response === false) {
throw new \RuntimeException('Connection closed by server');
}
if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) {
throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT');
}
if (!$this->_key_exchange($response)) {
return false;
}
$this->bitmap |= self::MASK_CONNECTED;
return true;
}