root/src/apps/codycam/FtpClient.cpp
/*
 * Copyright 1998-1999 Be, Inc. All Rights Reserved.
 * Copyright 2003-2019 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "FtpClient.h"

#include <stdlib.h>
#include <string.h>

#include <Catalog.h>
#include <Locale.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "FtpClient"


FtpClient::FtpClient()
        :
        FileUploadClient(),
        fState(0),
        fControl(NULL),
        fData(NULL)
{
}


FtpClient::~FtpClient()
{
        delete fControl;
        delete fData;
}


bool
FtpClient::ChangeDir(const string& dir)
{
        bool rc = false;
        int code, codeType;
        string cmd = "CWD ";
        string replyString;

        cmd += dir;

        if (dir.length() == 0)
                cmd += '/';

        if (_SendRequest(cmd) == true) {
                if (_GetReply(replyString, code, codeType) == true) {
                        if (codeType == 2)
                                rc = true;
                }
        }
        return rc;
}


bool
FtpClient::ListDirContents(string& listing)
{
        bool rc = false;
        string cmd, replyString;
        int code, codeType, numRead;
        char buf[513];

        cmd = "TYPE A";

        if (_SendRequest(cmd))
                _GetReply(replyString, code, codeType);

        if (_OpenDataConnection()) {
                cmd = "LIST";

                if (_SendRequest(cmd)) {
                        if (_GetReply(replyString, code, codeType)) {
                                if (codeType <= 2) {
                                        if (_AcceptDataConnection()) {
                                                numRead = 1;
                                                while (numRead > 0) {
                                                        memset(buf, 0, sizeof(buf));
                                                        numRead = fData->Receive(buf, sizeof(buf) - 1);
                                                        listing += buf;
                                                        printf("%s", buf);
                                                }
                                                if (_GetReply(replyString, code, codeType)) {
                                                        if (codeType <= 2)
                                                                rc = true;
                                                }
                                        }
                                }
                        }
                }
        }

        delete fData;
        fData = NULL;

        return rc;
}


bool
FtpClient::PrintWorkingDir(string& dir)
{
        bool rc = false;
        int code, codeType;
        string cmd = "PWD";
        string replyString;
        long i;

        if (_SendRequest(cmd) == true) {
                if (_GetReply(replyString, code, codeType) == true) {
                        if (codeType == 2) {
                                i = replyString.find('"');
                                if (i != -1) {
                                        i++;
                                        dir = replyString.substr(i, replyString.find('"') - i);
                                        rc = true;
                                }
                        }
                }
        }

        return rc;
}


bool
FtpClient::Connect(const string& server, const string& login,
        const string& passwd)
{
        bool rc = false;
        int code, codeType;
        string cmd, replyString;
        BNetAddress addr;

        delete fControl;
        delete fData;

        fControl = new BNetEndpoint();

        if (fControl->InitCheck() != B_NO_ERROR)
                return false;

        addr.SetTo(server.c_str(), "tcp", "ftp");
        if (fControl->Connect(addr) == B_NO_ERROR) {
                // read the welcome message, do the login

                if (_GetReply(replyString, code, codeType)) {
                        if (code != 421 && codeType != 5) {
                                cmd = "USER ";
                                cmd += login;
                                _SendRequest(cmd);

                                if (_GetReply(replyString, code, codeType)) {
                                        switch (code) {
                                                case 230:
                                                case 202:
                                                        rc = true;
                                                        break;

                                                case 331:  // password needed
                                                        cmd = "PASS ";
                                                        cmd += passwd;
                                                        _SendRequest(cmd);
                                                        if (_GetReply(replyString, code, codeType)) {
                                                                if (codeType == 2)
                                                                        rc = true;
                                                        }
                                                        break;

                                                default:
                                                        break;

                                        }
                                }
                        }
                }
        }

        if (rc == true)
                _SetState(ftp_connected);
        else {
                delete fControl;
                fControl = NULL;
        }

        return rc;
}


bool
FtpClient::PutFile(const string& local, const string& remote, ftp_mode mode)
{
        bool rc = false;
        string cmd, replyString;
        int code, codeType, rlen, slen, i;
        BFile infile(local.c_str(), B_READ_ONLY);
        char buf[8192];
        char sbuf[16384];
        char* stmp;

        if (infile.InitCheck() != B_NO_ERROR)
                return false;

        if (mode == binary_mode)
                cmd = "TYPE I";
        else
                cmd = "TYPE A";

        if (_SendRequest(cmd))
                _GetReply(replyString, code, codeType);

        try {
                if (_OpenDataConnection()) {
                        cmd = "STOR ";
                        cmd += remote;

                        if (_SendRequest(cmd)) {
                                if (_GetReply(replyString, code, codeType)) {
                                        if (codeType <= 2) {
                                                if (_AcceptDataConnection()) {
                                                        rlen = 1;
                                                        while (rlen > 0) {
                                                                memset(buf, 0, sizeof(buf));
                                                                memset(sbuf, 0, sizeof(sbuf));
                                                                rlen = infile.Read((void*)buf, sizeof(buf));
                                                                slen = rlen;
                                                                stmp = buf;
                                                                if (mode == ascii_mode) {
                                                                        stmp = sbuf;
                                                                        slen = 0;
                                                                        for (i = 0; i < rlen; i++) {
                                                                                if (buf[i] == '\n') {
                                                                                        *stmp = '\r';
                                                                                        stmp++;
                                                                                        slen++;
                                                                                }
                                                                                *stmp = buf[i];
                                                                                stmp++;
                                                                                slen++;
                                                                        }
                                                                        stmp = sbuf;
                                                                }
                                                                if (slen > 0) {
                                                                        if (fData->Send(stmp, slen) < 0)
                                                                                throw "bail";
                                                                }
                                                        }

                                                        rc = true;
                                                }
                                        }
                                }
                        }
                }
        }

        catch(const char* errorString)
        {
        }

        delete fData;
        fData = NULL;

        if (rc) {
                _GetReply(replyString, code, codeType);
                rc = codeType <= 2;
        }

        return rc;
}


bool
FtpClient::GetFile(const string& remote, const string& local, ftp_mode mode)
{
        bool rc = false;
        string cmd, replyString;
        int code, codeType, rlen, slen, i;
        BFile outfile(local.c_str(), B_READ_WRITE | B_CREATE_FILE);
        char buf[8192];
        char sbuf[16384];
        char* stmp;
        bool writeError = false;

        if (outfile.InitCheck() != B_NO_ERROR)
                return false;

        if (mode == binary_mode)
                cmd = "TYPE I";
        else
                cmd = "TYPE A";

        if (_SendRequest(cmd))
                _GetReply(replyString, code, codeType);

        if (_OpenDataConnection()) {
                cmd = "RETR ";
                cmd += remote;

                if (_SendRequest(cmd)) {
                        if (_GetReply(replyString, code, codeType)) {
                                if (codeType <= 2) {
                                        if (_AcceptDataConnection()) {
                                                rlen = 1;
                                                rc = true;
                                                while (rlen > 0) {
                                                        memset(buf, 0, sizeof(buf));
                                                        memset(sbuf, 0, sizeof(sbuf));
                                                        rlen = fData->Receive(buf, sizeof(buf));

                                                        if (rlen > 0) {

                                                                slen = rlen;
                                                                stmp = buf;
                                                                if (mode == ascii_mode) {
                                                                        stmp = sbuf;
                                                                        slen = 0;
                                                                        for (i = 0; i < rlen; i++) {
                                                                                if (buf[i] == '\r')
                                                                                        i++;
                                                                                *stmp = buf[i];
                                                                                stmp++;
                                                                                slen++;
                                                                        }
                                                                        stmp = sbuf;
                                                                }

                                                                if (slen > 0) {
                                                                        if (outfile.Write(stmp, slen) < 0)
                                                                                writeError = true;
                                                                }
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }

        delete fData;
        fData = NULL;

        if (rc) {
                _GetReply(replyString, code, codeType);
                rc = (codeType <= 2 && writeError == false);
        }
        return rc;
}


// Note: this only works for local remote moves, cross filesystem moves
// will not work
bool
FtpClient::MoveFile(const string& oldPath, const string& newPath)
{
        bool rc = false;
        string from = "RNFR ";
        string to = "RNTO ";
        string  replyString;
        int code, codeType;

        from += oldPath;
        to += newPath;

        if (_SendRequest(from)) {
                if (_GetReply(replyString, code, codeType)) {
                        if (codeType == 3) {
                                if (_SendRequest(to)) {
                                        if (_GetReply(replyString, code, codeType)) {
                                                if(codeType == 2)
                                                        rc = true;
                                        }
                                }
                        }
                }
        }
        return rc;
}


bool
FtpClient::Chmod(const string& path, const string& mod)
{
        bool rc = false;
        int code, codeType;
        string cmd = "SITE CHMOD ";
        string replyString;

        cmd += mod;
        cmd += " ";
        cmd += path;

        if (path.length() == 0)
                cmd += '/';
        printf(B_TRANSLATE("cmd: '%s'\n"), cmd.c_str());

        if (_SendRequest(cmd) == true) {
                if (_GetReply(replyString, code, codeType) == true) {
                        printf(B_TRANSLATE("reply: %d, %d\n"), code, codeType);
                        if (codeType == 2)
                                rc = true;
                }
        }
        return rc;
}


void
FtpClient::SetPassive(bool on)
{
        if (on)
                _SetState(ftp_passive);
        else
                _ClearState(ftp_passive);
}


bool
FtpClient::_TestState(unsigned long state)
{
        return ((fState & state) != 0);
}


void
FtpClient::_SetState(unsigned long state)
{
        fState |= state;
}


void
FtpClient::_ClearState(unsigned long state)
{
        fState &= ~state;
}


bool
FtpClient::_SendRequest(const string& cmd)
{
        bool rc = false;
        string ccmd = cmd;

        if (fControl != 0) {
                if (cmd.find("PASS") != string::npos) {
                        puts(B_TRANSLATE("PASS <suppressed>  (real password sent)"));
                } else {
                        puts(ccmd.c_str());
                }

                ccmd += "\r\n";
                if (fControl->Send(ccmd.c_str(), ccmd.length()) >= 0)
                        rc = true;
        }

        return rc;
}


bool
FtpClient::_GetReplyLine(string& line)
{
        bool rc = false;
        int c = 0;
        bool done = false;

        line = "";
                // Thanks to Stephen van Egmond for catching a bug here

        if (fControl != NULL) {
                rc = true;
                while (done == false && fControl->Receive(&c, 1) > 0) {
                        if (c == EOF || c == xEOF || c == '\n') {
                                done = true;
                        } else {
                                if (c == IAC) {
                                        fControl->Receive(&c, 1);
                                        switch (c) {
                                                unsigned char treply[3];
                                                case WILL:
                                                case WONT:
                                                        fControl->Receive(&c, 1);
                                                        treply[0] = IAC;
                                                        treply[1] = DONT;
                                                        treply[2] = c;
                                                        fControl->Send(treply, 3);
                                                break;

                                                case DO:
                                                case DONT:
                                                        fControl->Receive(&c, 1);
                                                        fControl->Receive(&c, 1);
                                                        treply[0] = IAC;
                                                        treply[1] = WONT;
                                                        treply[2] = c;
                                                        fControl->Send(treply, 3);
                                                break;

                                                case EOF:
                                                case xEOF:
                                                        done = true;
                                                break;

                                                default:
                                                        line += c;
                                                break;
                                        }
                                } else {
                                        // normal char
                                        if (c != '\r')
                                                line += c;
                                }
                        }
                }
        }

        return rc;
}


bool
FtpClient::_GetReply(string& outString, int& outCode, int& codeType)
{
        bool rc = false;
        string line, tempString;

        //
        // comment from the ncftp source:
        //

        /* RFC 959 states that a reply may span multiple lines.  A single
         * line message would have the 3-digit code <space> then the msg.
         * A multi-line message would have the code <dash> and the first
         * line of the msg, then additional lines, until the last line,
         * which has the code <space> and last line of the msg.
         *
         * For example:
         *      123-First line
         *      Second line
         *      234 A line beginning with numbers
         *      123 The last line
         */

        rc = _GetReplyLine(line);
        if (rc == true) {
                outString = line;
                puts(outString.c_str());
                outString += '\n';
                tempString = line.substr(0, 3);
                outCode = atoi(tempString.c_str());

                if (line[3] == '-') {
                        rc = _GetReplyLine(line);
                        while (rc == true) {
                                outString += line;
                                puts(outString.c_str());
                                outString += '\n';
                                // we're done with nnn when we get to a "nnn blahblahblah"
                                if ((line.find(tempString) == 0) && line[3] == ' ')
                                        break;

                                rc = _GetReplyLine(line);
                        }
                }
        }

        if (!rc && outCode != 421) {
                outString += B_TRANSLATE("Remote host has closed the connection.\n");
                outCode = 421;
        }

        if (outCode == 421) {
                delete fControl;
                fControl = NULL;
                _ClearState(ftp_connected);
        }

        codeType = outCode / 100;

        return rc;
}


bool
FtpClient::_OpenDataConnection()
{
        string host, cmd, replyString;
        unsigned short port;
        BNetAddress addr;
        int i, code, codeType;
        bool rc = false;
        struct sockaddr_in sa;

        delete fData;
        fData = NULL;

        fData = new BNetEndpoint();

        if (_TestState(ftp_passive)) {
                // Here we send a "pasv" command and connect to the remote server
                // on the port it sends back to us
                cmd = "PASV";
                if (_SendRequest(cmd)) {
                        if (_GetReply(replyString, code, codeType)) {

                                if (codeType == 2) {
                                        //  It should give us something like:
                                        // "227 Entering Passive Mode (192,168,1,1,10,187)"
                                        int paddr[6];
                                        unsigned char ucaddr[6];

                                        i = replyString.find('(');
                                        i++;

                                        replyString = replyString.substr(i,
                                                replyString.find(')') - i);
                                        if (sscanf(replyString.c_str(), "%d,%d,%d,%d,%d,%d",
                                                &paddr[0], &paddr[1], &paddr[2], &paddr[3],
                                                &paddr[4], &paddr[5]) != 6) {
                                                        // Cannot do passive.
                                                        // Do a little harmless rercursion here.
                                                        _ClearState(ftp_passive);
                                                        return _OpenDataConnection();
                                                }

                                        for (i = 0; i < 6; i++)
                                                ucaddr[i] = (unsigned char)(paddr[i] & 0xff);

                                        memcpy(&sa.sin_addr, &ucaddr[0], (size_t) 4);
                                        memcpy(&sa.sin_port, &ucaddr[4], (size_t) 2);
                                        addr.SetTo(sa);
                                        if (fData->Connect(addr) == B_NO_ERROR)
                                                rc = true;

                                }
                        }
                } else {
                        // cannot do passive.  Do a little harmless rercursion here
                        _ClearState(ftp_passive);
                        rc = _OpenDataConnection();
                }

        } else {
                // Here we bind to a local port and send a PORT command
                if (fData->Bind() == B_NO_ERROR) {
                        char buf[255];

                        fData->Listen();
                        addr = fData->LocalAddr();
                        addr.GetAddr(buf, &port);
                        host = buf;

                        i = 0;
                        while (i >= 0) {
                                i = host.find('.', i);
                                if (i >= 0)
                                        host[i] = ',';
                        }

                        sprintf(buf, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff);
                        cmd = "PORT ";
                        cmd += host;
                        cmd += buf;
                        _SendRequest(cmd);
                        _GetReply(replyString, code, codeType);
                        // PORT failure is in the 500-range
                        if (codeType == 2)
                                rc = true;
                }
        }

        return rc;
}


bool
FtpClient::_AcceptDataConnection()
{
        BNetEndpoint* endPoint;
        bool rc = false;

        if (_TestState(ftp_passive) == false) {
                if (fData != NULL) {
                        endPoint = fData->Accept();
                        if (endPoint != NULL) {
                                delete fData;
                                fData = endPoint;
                                rc = true;
                        }
                }

        }
        else
                rc = true;

        return rc;
}