Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FTP Debug Transcript #19

Closed
tsmgeek opened this issue Sep 9, 2021 · 5 comments
Closed

FTP Debug Transcript #19

tsmgeek opened this issue Sep 9, 2021 · 5 comments

Comments

@tsmgeek
Copy link

tsmgeek commented Sep 9, 2021

Hi, ive come across your project which is looking promising compared to the base FTP stuff in PHP.

Got a question regarding its capabilities. For years ive been using the linked library (heavily modified since that release) because I wanted to get full FTP debug and response messages returned by the server which I never found a way to do it via the main PHP library.
Am I right in saying this is still not possible and instead we would need to use say (https://github.com/lazzard/ftp-bridge) and build atop all the methods we need as its seems far more promising than the Sockets implementation in the library im currently using.

https://www.phpclasses.org/package/1743-PHP-FTP-client-in-pure-PHP.html

@AmraniCh
Copy link
Member

AmraniCh commented Sep 10, 2021

Hey, Thanks for opening this issue and for sharing your ideas and suggestions.

About the motioned FTP library.

I've tried using the library that you use a little bit and I see that this library works just fine for a regular FTP client user, my only complaint is that there are two classes that do almost the same thing except the first one requires sockets extension enabled (for active mode connection) and the second not required that and uses the stream wrappers to create client connections.

Another thing which I don't like is the debugging information, this library doesn't log the server's responses as expected for example one of the most common response formats to PASV command is like 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). while the library just logs a custom message like Passive mode on, and there a lot of other custom logging messages like this which not the messages that the FTP server nsent.

The purposes of creating the Lazzard\FtpBridge library.

Lazzard\FtpBridge library is not intended to react as an FTP Client, it can be used for example behind an FTP client library to provide underlying base tools that let you freely communicate with FTP servers without finding yourself struggling with the sockets/streams logic.

To more clarify that I write for you a simple FTP client class that uses Lazzard\FtpBridge internally and it deson't requires additonal extensions like FTP or sockets 👍

FtpClient class that uses FtpBridge behind the scenes. (>= PHP v7.4.0)
<?php

require dirname(__DIR__) . "/vendor/autoload.php";

use Lazzard\FtpBridge\FtpBridge;
use Lazzard\FtpBridge\Logger\ArrayLogger;
use Lazzard\FtpBridge\Logger\LogLevel;
use Lazzard\FtpBridge\Exception\FtpBridgeException;

class FtpClientException extends \Exception implements Throwable {

}

/**
 * FtpClient class that uses FtpBridge behind the scenes.
 */
class FtpClient {
    protected const CRLF = "\r\n";
    protected FtpBridge $ftp;
    protected ?ArrayLogger $logger = null;
    protected bool $verbose;

    public function __construct($verbose = false)
    {
        if ($this->verbose = $verbose) {
            $this->logger = new ArrayLogger;
            LogLevel::setCommand('PUT');
            LogLevel::setInfo('GET');
            LogLevel::setError('GET');
        }

        $this->ftp = new FtpBridge($this->logger);
    }

    public function connect($hostname, $port = 21) : bool
    {
        return $this->call('connect', [$hostname, $port]);
    }

    public function login($username, $password)  : bool
    {
       return $this->call('login', [$username, $password]);
    }

    public function passive($pasv = true) : bool
    {
        return $this->call($pasv ? 'openPassive' : 'openActive');
    }

    public function list($directory) : ?array
    {
        $this->call('send', [sprintf("LIST %s", $directory)]);
        $this->call('receive');

        if (!$this->ftp->response->hasCode(150, 125)) {
            return false;
        }

        $list = $this->call('receiveData');

        // remove last CRLF
        $list = rtrim($list, self::CRLF);

        $this->call('receive');

        if (!$this->ftp->response->hasCode(226, 250)) {
            return false;
        }

        return explode(self::CRLF, $list);
    }

    public function quit() : void
    {
        $this->call('send', ['QUIT']);
        $this->call('receive');
    }

    public function getSession() : array
    {
        if (!$this->verbose) {
            throw new FtpClientException('Verbose option is not enabled.');
        }

        return $this->logger->getLogs();
    }

    protected function call($functionName, $arguments = null)
    {
        try {
            return call_user_func_array([$this->ftp, $functionName], $arguments ?: []);
        } catch(FtpBridgeException $ex) {
            throw new FtpClientException($ex->getMessage());
        }
    }
}

try {
    // change these with your credentials
    $hostname = 'host';
    $username = 'username';
    $password = 'password';

    $client = new FtpClient(true);

    $client->connect($hostname);
    $client->login($username, $password);
    $client->passive(true);

    $client->list('.');

    $client->quit();

    print_r($client->getSession());
} catch(FtpClientException $ex) {
    print_r($ex->getMessage());
}

Also, in comparison to your mentioned library, Lazzard\FtpBridge doesn't require PHP sockets extension (even it support active mode connection), because it uses PHP socket streams wrappers (stream_socket_* functions) which are somehow high-level functions in comparison to sockets extension functions, I've taken this choice because I think we haven't to configure so many things to just create just a basic TCP connection to an FTP host.

Lazzard\FtpClient.

Lazzard\FtpClient is based on the FTP extension, and it is just a low-level abstraction library that provides helper methods out of the box that a regular FTP user commonly needs.

I was thinking one time to integrate Lazzard\FtpBridge with Lazzard\FtpClient to add support of some FTP command-based features like MLSD which is not supported by old PHP versions and becomes supported since v7.2 ftp_mlsd, but I've changed my mind due to some side effects that may be produced (for example if I a user calls an API method that runs Lazzard\FtpBridge then a new TCP connection will be created and we have to login again to the server, and for some FTP servers the number of parallels logins by the same user can be restricted to limit the resources used by this user, and I think there is no way to reuse for example the same resource stream used by ftp_connect or ftp_ssl_connect to avoid this).

Conclusion.

Lazzard\FtpBridge is just a first release candidate for now and we can't rely on it until a stable version is released, after that, we can build a lot of ideas on top of it, it can be used behind an FTP client as I've said earlier or we can for example build an FTP debugger/tester tool like this one here.

Sorry if you find my answer long, I've understood your points very well and tried to give you as well some of my ideas of how this libraries can be improved and used in other projects. Thank you!

@AmraniCh AmraniCh pinned this issue Sep 10, 2021
@tsmgeek
Copy link
Author

tsmgeek commented Sep 10, 2021

Thank you for your extensive answer, ive got a lot to go though.

I think it may be a good idea for me to add my updates to that old library as it does fix many of the limitations of the original, I log all the server requests and replies from start to finish and it can output it all to a filehandle etc.
We have been using the library for 10+years to send many TB of data per month, so its fairly well battle tested but its lacking support for FTPS.

  • recent support for MLSD directory lists (also LIST/NLST)
  • handle passing filehandles to put()
  • full control channel logging
  • connection alive checks and ABOR where needed after errors
  • login ACCT support
  • NOOP support to keep sessions alive
  • add support for HOST (RFC7175)
  • Only IPV4 support currently but adding IPV6 should not be too difficult

I am not sure the reason the original developer took to use sockets, maybe back in PHP4/5 days there were limitations with streams or did not exist, also could be they gave additional performance.
Now I use non-blocking modes in sockets as we noticed that odd servers can drop PASV connections etc and it would just hang and ignore all timeouts, rare but it happened, might be where connections disappear but no TCP RST packet is sent.

I will look to split it into a repo on my profile and then you can dig around and see if there are any useful parts or crossover.

@AmraniCh
Copy link
Member

We have been using the library for 10+years to send many TB of data per month, so its fairly well battle tested but its lacking support for FTPS.

The risks of using plain FTP are almost similar to use like HTTP, all the FTP session inputs/outputs can be exposed to an attacker, and if you doing some real business you have to consider moving to FTPS or even more secure using SFTP.

I am not sure the reason the original developer took to use sockets, maybe back in PHP4/5 days there were limitations with streams or did not exist, also could be they gave additional performance.

You're right this library is old and I've received some PHP errors when I've used it due to some deprecated functions and syntax, and you're right also about the performance but the performance gain when using the sockets extension is small and not very considerable.

Now I use non-blocking modes in sockets as we noticed that odd servers can drop PASV connections etc and it would just hang and ignore all timeouts, rare but it happened, might be where connections disappear but no TCP RST packet is sent.

I don't know honestly about this rare case, and Lazzard\FtpBridge still does not support non-blocking TCP operations, however, thanks for this info.

I will look to split it into a repo on my profile and then you can dig around and see if there are any useful parts or crossover.

Yeah, go ahead, and I'll be very happy to do this.

@tsmgeek
Copy link
Author

tsmgeek commented Sep 11, 2021 via email

@AmraniCh
Copy link
Member

I am well aware that its easy to capture (wireshark etc) , but we only
transfer images/video (nothing sensitive) and it's the client that dictates
how unfortunately. I've had this argument many a time before. Some are SFTP
others S3 etc but the bulk still plain FTP. We support receiving all
FTP/FTPS/SFTP/HTTPS.

Understood and good lock in your business.

If you want to contribute to Lazzard\FtpBridge you're welcome, Lazzard is an online organization that involves people whos interested in open source software (OSS) and have the passion to work with other developers to share their knowledge and developing things that make people life easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants