* 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) {
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:
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;
}
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 = "";
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 {
if (c != '\r')
line += c;
}
}
}
}
return rc;
}
bool
FtpClient::_GetReply(string& outString, int& outCode, int& codeType)
{
bool rc = false;
string line, tempString;
* 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';
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)) {
cmd = "PASV";
if (_SendRequest(cmd)) {
if (_GetReply(replyString, code, codeType)) {
if (codeType == 2) {
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) {
_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 {
_ClearState(ftp_passive);
rc = _OpenDataConnection();
}
} else {
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);
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;
}