Subversion Repositories tpanel

Rev

Rev 112 | Blame | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2022 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include <fstream>

#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <unistd.h>

#include "tfsfreader.h"
#include "tsocket.h"
#include "tconfig.h"
#include "terror.h"
#include "tresources.h"
#include "readtp4.h"

#define FTP_PORT    21

#define FTP_CMD_USER    0
#define FTP_CMD_PASS    1
#define FTP_CMD_PASV    2
#define FTP_CMD_TYPE    3
#define FTP_CMD_RETR    4
#define FTP_CMD_QUIT    5

using std::string;
using std::vector;

TFsfReader::TFsfReader()
{
    DECL_TRACER("TFsfReader::TFsfReader()");

    mFtpCmds = {
        { "USER", 331 },    // 0
        { "PASS", 230 },    // 1
        { "PASV", 227 },    // 2
        { "TYPE", 200 },    // 3
        { "RETR", 150 },    // 4
        { "QUIT", 221 }     // 5
    };
}

TFsfReader::~TFsfReader()
{
    DECL_TRACER("TFsfReader::~TFsfReader()");

    if (mFtp)
        delete mFtp;

    if (mFtpData)
        delete mFtpData;
}

bool TFsfReader::sendCommand(int cmd, const string& param)
{
    DECL_TRACER("TFsfReader::sendCommand(int cmd, const string& param)");

    if (cmd < 0 || cmd > (int)mFtpCmds.size() || !mFtp)
        return false;

    string buf;

    if (!param.empty())
        buf = mFtpCmds[cmd].cmd + "\r\n";
    else
        buf = mFtpCmds[cmd].cmd + " " + param + "\r\n";

    if (mFtp->send((char *)buf.c_str(), buf.length()) != TSocket::npos)
        return false;

    char buffer[128];
    size_t size;
    memset(buffer, 0, sizeof(buffer));

    if ((size = mFtp->receive(buffer, sizeof(buffer))) == TSocket::npos)
        return false;

    mLastCmdBuffer = buffer;
    int code = 0;

    if (mFtpCmds[cmd].success && !isdigit(buffer[0]))
    {
        MSG_ERROR("Unexpected answer: " << buffer);
        return false;
    }
    else if (mFtpCmds[cmd].success)
        code = atoi(buffer);

    if (code != mFtpCmds[cmd].success)
    {
        MSG_ERROR("FTP error: " << buffer);
        return false;
    }

    MSG_DEBUG("FTP command " << mFtpCmds[cmd].cmd << " returned: " << buffer);
    return true;
}

bool TFsfReader::copyOverFTP(const string& fname, const string& target)
{
    DECL_TRACER("TFsfReader::copyOverFTP(const string& fname, const string& target, const string& user, const string& pw)");

    if (mFtp)
        delete mFtp;

    mFtp = new TSocket(TConfig::getController(), FTP_PORT);

    string buf, filename;
    char *f, buffer[128];
    size_t size;

    if (!mFtp->connect())
        return false;

    // Server should answer with: 220 <message>
    memset(buffer, 0, sizeof(buffer));

    if ((size = mFtp->receive(buffer, sizeof(buffer))) == TSocket::npos)
    {
        mFtp->close();
        return false;
    }

    if (!startsWith(buffer, "220 "))
    {
        MSG_ERROR("Unexpected answer: " << buffer);
        mFtp->close();
        return false;
    }

    // We send the authentication
    if (!sendCommand(FTP_CMD_USER, TConfig::getFtpUser()))
    {
        mFtp->close();
        return false;
    }

    // Here we send the password
    if (!sendCommand(FTP_CMD_PASS, TConfig::getFtpPassword()))
    {
        mFtp->close();
        return false;
    }

    // Switching to passive mode
    if (!sendCommand(FTP_CMD_PASV, ""))
    {
        mFtp->close();
        return false;
    }

    size_t pos = mLastCmdBuffer.find_last_of("(");

    if (pos != string::npos)
    {
        size_t end = mLastCmdBuffer.find_last_of(")");

        if (end != string::npos)
        {
            string info = mLastCmdBuffer.substr(pos + 1, end-pos-2);
            MSG_DEBUG("PASV " << info);
            vector<string> parts = StrSplit(info, ",");

            if (parts.size() < 6)
            {
                MSG_ERROR("PASV command failed!");
            }
            else
            {
                mDataPort = atoi(parts[4].c_str()) * 256 + atoi(parts[5].c_str());
                mPassive = true;
            }
        }
    }
    else
    {
        MSG_ERROR("Unexpected response from FTP server!");
        mFtp->close();
        return false;
    }

    // We connect to the data port
    if (mFtpData)
        delete mFtpData;

    mFtpData = new TSocket(TConfig::getController(), mDataPort);

    if (!mFtpData->connect())
    {
        mFtp->close();
        return false;
    }

    // Switch to image mode
    if (!sendCommand(FTP_CMD_TYPE, "I"))
    {
        mFtp->close();
        return false;
    }

    // GET a file
    if (!sendCommand(FTP_CMD_RETR, fname))
    {
        mFtp->close();
        return false;
    }

    size = 0;
    pos = mLastCmdBuffer.find_last_of("(");

    if (pos != string::npos)
    {
        string info = mLastCmdBuffer.substr(pos + 1);
        MSG_DEBUG("RETR size: " << info);
        size = atoi(info.c_str());
    }
    else
    {
        MSG_ERROR("Unexpected response: " << mLastCmdBuffer);
        mFtp->close();
        return false;
    }

    // FIXME: Add code to receive file
    f = new char[size+1];

    if (mFtpData->readAbsolut(f, size) == TSocket::npos)
    {
        MSG_ERROR("Error receiving file " << fname << " from FTP server!");
        mFtpData->close();
        mFtp->close();
        delete[] f;
        return false;
    }

    // Write the file into place
    try
    {
        std::ofstream file;

        file.open(target, std::ios_base::binary);
        file.write(f, size);
        file.close();
    }
    catch (std::exception& e)
    {
        MSG_ERROR("Error on file " << target << ": " << e.what());
        mFtpData->close();
        mFtp->close();
        delete[] f;
        return false;
    }

    // QUIT from FTP-server
    if (!sendCommand(FTP_CMD_QUIT, ""))
    {
        mFtp->close();
        mFtpData->close();
        return false;
    }

    mFtp->close();
    mFtpData->close();
    return true;
}

bool TFsfReader::unpack(const string& fname, const string& path)
{
    DECL_TRACER("TFsfReader::unpack(const string& fname, const string& path)");

    if (fname.empty() || path.empty())
    {
        MSG_ERROR("Invalid parameters!");
        return false;
    }

    reader::ReadTP4 readtp4(fname, path);
    return readtp4.doRead();
}