FTPS in Laravel not working (SSL write failed) - php

So I am trying to transfer a file via FTPS in Laravel. I am using this code to create a disk
public function disk(){
$protocol = $this->protocol;
if($this->protocol == "ftps"){
$protocol = "ftp";
}
$disk = Storage::build([
'driver' => $this->protocol,
'host' => $this->ip,
'username' => $this->username,
'password' => $this->password,
'ssl' => true,
'passive' => true
]);
return $disk;
}
And I am calling the function within this code-block and try to upload a file:
public function upload(ImportedImage $image){
$filepath = "path/to/file";
if($this->protocol == "ftp" || $this->protocol == "ftps"){
$this->disk()->putFileAs($this->folder, $filepath, $image->original_name);
return "ok";
}
}
But I get this error
ErrorException: ftp_fput(): SSL write failed in /var/www/html/zendee/vendor/league/flysystem/src/Adapter/Ftp.php:275
The object I am accessing has correct a username/password combination, I can access the server via cyberduck/filezilla, so that's not the issue. The protocol is "ftps", (so it gets changed to "ftp" in the "disk" function).
I have already looked into similar problems which where solved by the "passive" parameter. I tried every possible combination of the "ssl" and "passive" option - didn't help unfortunately.
It also said somewhere that I should configure my FTP-Server to allow ssl-only, so I also did that.
Thanks for every kind of help.

Related

PHP AWS S3 SDK retry on network connections errors

I'm using AWS SDK V3 for PHP. Sometimes when I make calls to AWS S3 I get errors like 400 errors Bad Request RequestTimeout (client): Your socket connection to the server was not read from or written to within the timeout period due to network problem even when the object I'm trying to interact with is there. What I need to do is to implement a retry mechanism. I wonder if we can do it simply with an option in the AWS SDK to specify the number of times we want a retry after an error.
I know that I can do that with simple try catch and retry, but I'm thinking may be the SDK already provides a much cleaner way to do that.
I already found static function Middleware::retry() but I have no idea how to use it.
You can specify the number of retries when constructing a new instance of any AWS service client class:
$client = new Aws\EC2\Ec2Client([
'region' => 'eu-central-1',
'retries' => 3
]);
https://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html#retries
You can use something like this,
public function get_s3_connection(){
$sdk = new \Aws\Sdk([
'region' => 'us-east-1',
'version' => 'latest',
'credentials' => $credentials
]);
$s3_connection = $sdk->createS3([
'retries' => 3,
'http' => [
'connect_timeout' => 15,
'timeout' => 20
],
'endpoint' => 'https://bucket.0123xyz.s3.us-east-1.amazonaws.com'
]);
return $s3_connection;
}
In the above code the retries => 3 means it will try 3 times and then stop trying.
You can also set the timeout and connect_timeout as show above.
You can also retry manually by surrounding the method with do-while loop, as shown below,
public function s3_connect_with_retry($total_retry = 3){
$retry_count = 0;
$do_retry = false;
do{
try{
$s3_connection = $this->get_s3_connection();
}catch(Exception $exception){
$retry_count++;
$do_retry = true;
}
}while($total_retry < $retry && $do_retry == true;);
}
When the connection breaks and throws exception the $do_retry value will become true and the $retry_count will increase. based on the $do_retry flag the retry happens until the $total_retry count is reached.

PHP ZF2 Mysql has gone away

Hi I have a php daemon that handle request from rabbitmq
After a day, it can no longer execute due to error MySQL has gone away.
PHP Warning: PDOStatement::execute(): MySQL server has gone away in /var/www/daemon/www/vendor/zendframework/zendframework/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 239
PHP Warning: PDOStatement::execute(): Error reading result set\'s header in /var/www/daemon/www/vendor/zendframework/zendframework/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 239
I didn't use doctrine, instead I send my \Zend\Db\Adapter\Adapter to a db wrapper class with below function.
public static function executeScalar($statement, $parameters, \Zend\Db\Adapter\Adapter $dbAdapter)
{
$dbResult = new DbResult();
if (! $statement) {
$dbResult->addError('No statement given');
return $dbResult;
}
$stmt = $dbAdapter->createStatement();
$stmt->prepare($statement);
foreach ($parameters as $key => &$param) {
$stmt->getResource()->bindParam($key + 1, $param[0], $param[1]);
}
try {
$result = $stmt->execute();
$dbResult->setResult($result);
} catch (\Zend\Db\Adapter\ExceptionInterface $e) {
$dbResult->addError('DB Error');
$message = $e->getPrevious() ? $e->getPrevious()->getMessage() : $e->getMessage();
$dbResult->addError($message);
} catch (\Zend\Db\Adapter\Exception $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
} catch (\PDOException $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
} catch (\Exception $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
}
$stmt->getResource()->closeCursor();
return $dbResult;
}
DbResult is my own db result wrapper class it mainly check whether it return empty, what's the error, how many rows, etc.
Here is my database.local.php configuration
return array(
'service_manager' => array(
'factories' => array(
'mysql' => function ($sm)
{
return new Zend\Db\Adapter\Adapter(array(
'driver' => 'PdoMysql',
'hostname' => 'localhost',
'database' => 'daemon',
'username' => 'daemon',
'password' => 'password',
'driver_options' => array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::MYSQL_ATTR_LOCAL_INFILE => true
)
));
},
)
)
)
So everytime I want to execute a sql I do this inside controller or any other class ( Just an example )
$service = $this->getServiceLocator();
$dbAdapter = $service->get('mysql');
$get = \Db\Database::executeScalar('SELECT * FROM mytable WHERE id <= ?', array(10), $dbAdapter);
It seems I cannot catch the warning, and is there a way to force reconnect or perhaps I just do a disconnect after each request ?
Will this works, to handle the error ?
On every new request I do this
$dbAdapter->getDriver()->getConnection()->connect();
At the end of request I do this
$dbAdapter->getDriver()->getConnection()->disconnect();
Yes, I check the persistent connection option, but I also not fond of it.
I find the problem, it cause by mysql server close idle connection after 'wait timeout'. when mysql closing the idle connection, PDO will not receive any event so the next time you initiate a query it will return Mysql has gone away error.
For http request this is acceptable since after the server response the request it will stop/exit php execution which close all connection to database.
For daemon/service this is not acceptable since most of the time it waiting for client request (idle). My solution is to close the connection everytime it finish handling client request. e.g :
while (true) {
//listen to rabbitmq queue
//...
//do something base on client request from rabbitmq queue
//...
//close the connection whether it use database or not
//connection will be reconnected when we call $service->get('mysql');
$service = $this->getServiceLocator();
$dbAdapter = $service->get('mysql');
$dbAdapter->getDriver()->getConnection()->disconnect();
}
You can create a persistent connection to your database but be warned that creating a persistent connection should not be the first solution to look for. Be sure to do some research on the subject before trying it. You can find some documentation here :
http://php.net/manual/en/pdo.connections.php#example-954
On the other hand, you should look for the queries sent so the reason of the gone away message is not caused by the mysql server recieving a packet too large (ex: inserting a large blob). Because if it is, the connection will still close unexpectedly.

unable to call ssh2_connect() callbacks

I am using the ssh2_connect() method to establish a connection with a remote server. A connection is establishing correctly if I am providing the right hostname and port number. When unable to connect due to wrong credentials, I am trying to call a callback-function but the way I try it isn't calling the callback after connection failure.
This i the code I tried:
$callbacks = array(
'ignore' => array($this, 'callbackSshDisconnect'),
'debug' => array($this, 'callbackSshDisconnect'),
'macerror' => array($this, 'callbackSshDisconnect'),
'disconnect' => array($this, 'callbackSshDisconnect'),
);
ssh2_connect($hostName,$port,array('hostkey', 'ssh-rsa'),$callbacks);
public function callbackSshDisconnect($reason, $message, $language) {
$this->log('disconnected');
$this->log($reason);die;
}
What I am doing wrong?
When ssh2_connect fails because of wrong host, port, and so on, it doesn't call any callbacks.
What is does, instead, is returning false
PHP documentation says :
Returns a resource on success, or FALSE on error.
$result = ssh2_connect($hostName,$port,array('hostkey' => 'ssh-rsa'),$callbacks);
if ($result === false)
exit("ssh2_connect failed");
Also, reading the doc, your
array('hostkey', 'ssh-rsa')
should be
array('hostkey' => 'ssh-rsa')
There's nothing you are doing wrong apart from the typo: array('hostkey', 'ssh-rsa') should be array('hostkey' => 'ssh-rsa'). The function ssh_connect() just returns false for a connection failure; callbacks simply are not initiated yet when the wrong credentials are being used.
There are solutions (e.g. as suggested by rubo77 below) but the one I find will give you most control, and allow you to do what you want (e.g. tracking MAC errors), is to use the phpseclib library (http://phpseclib.sourceforge.net/ssh/intro.html) for ssh connections and control. It gives very fine control of commands, and also includes logging.
It's not the simplest solution, but you can control as finely as if you are at the keyboard/terminal directly.
You have control over timeouts and can use callback on commands. But if you want better control, use read() and write() and you can monitor for disconnects or other issues. Check the documentation on logging with phpseclib: you can either log and parse the log, or call getLastError().
Log files will show 'Connection closed by server' on disconnects for example, but will also tell you if you're using an unsupported authentication mode on login etc.
Or for more, read the code: here, for example, are the disconnect reasons:
$this->disconnect_reasons = array(
1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
4 => 'NET_SSH2_DISCONNECT_RESERVED',
5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
);
Even if you would use working code, like this:
$callbacks = array(
'ignore' => 'callback',
'debug' => 'callback',
'macerror' => 'callback',
'disconnect' =>'callback',
);
$session = ssh2_connect($hostName, $port, array('hostkey' => 'ssh-rsa'), $callbacks);
if($session === false) {
print('Connection failed' . PHP_EOL);
exit(1);
}
if(!ssh2_auth_pubkey_file(
$session,
"user",
'/home/user/.ssh/id_rsa.pub',
'/home/user/.ssh/id_rsa'
)) {
print('Failed to authenticate session' . PHP_EOL);
exit(2);
}
// Check this great answer:
// http://stackoverflow.com/a/12094837/171318
$stream = ssh2_exec($session, "ls -al");
stream_set_blocking($stream, true);
$stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
echo stream_get_contents($stream);
function callback($reason, $message, $language) {
print("callback:" . PHP_EOL);
print(" " . $message . PHP_EOL);
print(" " . $reason . PHP_EOL);
}
the callbacks won't get called in a usual application. This is because they were called just under certain circumstances named in the manual page
The disconnect callback for example get's only called if a SSH2_MSG_DISCONNECT packet is received. Such a packet is not received unless the server closes the connection which will usually not happen.
When you are unable to connect due to wrong credentials then you won't get any callbacks anyway, ssh2_connect() just returns false in this case.
If you really want to catch the reason of the failure, you can use output buffer handler to extract the error message (assuming, you have error reporting set on):
set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
list($command,$errormessage)=explode(":",$errstr,2);
echo "Error ($errno):", $errormessage."<br><br>";
});
$connection = ssh2_connect($hostName,$port);
restore_error_handler();

ssh2_auth_pubkey_file authentication always fails

I'm trying to connect to another machine using PHP's ssh2 functions. I know the ssh keys have been created with no passwords and are distributed correctly, I can ssh user#host in the terminal on my machine to the server.
The PHP function tries to connect to a ip address using an ssh key file:-
function minnerConnect($miner_serial) {
$port = '7822';
$miner_ip = $this->getMinerIp($miner_serial);
$methods = array(
'kex' => 'diffie-hellman-group1-sha1',
'hostkey' => 'ssh-dss',
'client_to_server' => array(
'crypt' => '3des-cbc',
'mac' => 'hmac-md5',
'comp' => 'none'),
'server_to_client' => array(
'crypt' => '3des-cbc',
'mac' => 'hmac-md5',
'comp' => 'none'));
$connection = ssh2_connect($miner_ip, $port, $methods);
if (ssh2_auth_pubkey_file($connection, 'root',
'/root/.ssh/id_dsa.pub',
'/root/.ssh/id_dsa','')) {
echo "Public Key Authentication Successful\n";
} else {
echo "Public Key Authentication Failed";
}
but the error shown is:-
( ! ) Warning: ssh2_auth_pubkey_file(): Authentication failed for root using public key: Callback returned error in /var/www/application/models/miner_model.php on line 95
line 95 is '/root/.ssh/id_dsa','')) {.
Can anybody suggest a fix?
The error in this case was that the keys were generated by the root user, but they need to be accessible by the web server group/owner www-data.
I didn't like the idea of keeping ssh keys in a web folder open to www-data, so I moved the key files to a new user's home directory (/home/keyuser/) then made them accessible to www-data. Authentication was successful.
Even though the original error was saying it found the file, it couldn't read the file.
A better debug method is to try reading the file via php:
$prv_key = file_get_contents('/var/www/application/files/id_dsa');
print "<pre>";
var_export($prv_key);
print "</pre>";

How do I verify that Zend\Mail has an open IMAP Stream?

My host company will not enable the IMAP extension on our web server so I cannot utilize IMAP functions. I am trying to use Zend\Mail as a replacement, but I have been unable to map out a one to one ratio of native IMAP functions with Zend\Mail functions.
Part of my scripts checks to see if an open IMAP stream exists and if it does, to close it and then reopen it. Here's how my php script does it using IMAP functions:
function open($box='INBOX') {
if ($this->mbox)
$this->close();
$args = array($this->srvstr.$this->mailbox_encode($box),
$this->getUsername(), $this->getPassword());
// Disable Kerberos and NTLM authentication if it happens to be
// supported locally or remotely
if (version_compare(PHP_VERSION, '5.3.2', '>='))
$args += array(NULL, 0, array(
'DISABLE_AUTHENTICATOR' => array('GSSAPI', 'NTLM')));
$this->mbox = call_user_func_array('imap_open', $args);
return $this->mbox;
}
function close($flag=CL_EXPUNGE) {
imap_close($this->mbox, $flag);
}
How do I do the same thing with Zend\Mail v2.2?
I have tried using this code, but it errors out with Status 500 and just returns a blank screen:
function open($box='INBOX') {
if ($mail)
$this->close();
$args = array($this->srvstr.$this->mailbox_encode($box),
$this->getUsername(), $this->getPassword());
// Disable Kerberos and NTLM authentication if it happens to be
// supported locally or remotely
if (version_compare(PHP_VERSION, '5.3.2', '>='))
$args += array(NULL, 0, array(
'DISABLE_AUTHENTICATOR' => array('GSSAPI', 'NTLM')));
$this->getProtocol();
$protocol = $this->ht['protocol'];
if ($protocol=="Imap")
$mail = new Zend\Mail\Storage\Imap($args);
else
$mail = new Zend\Mail\Storage\Pop3($args);
return $mail;
}
function close() {
$mail->close();
$mail='';
}
Your $args are wrong. That's how the params are written:
$mail = new Zend_Mail_Storage_Pop3(array('host' => 'localhost',
'user' => 'test',
'password' => 'test'));

Categories