// // File: hzFtpClient.cpp // // Legal Notice: This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com) // // The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free // Software Foundation, either version 3 of the License, or any later version. // // The HadronZoo C++ Class Library 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses. //
#include <fstream>
#include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> #include <dirent.h> #include <netdb.h> #include <utime.h>
#include "hzTextproc.h" #include "hzFtpClient.h" #include "hzProcess.h"
using namespace std ;
/* ** General FTP functions */
hzEcode _checkpath (hzString& ult, const hzString& current, const hzString& mod) { // Predicts the final directory path when a path is operated on by a change directory command. // // Arguments: 1) ult The result of the current path operated on by the moderator // 2) current The current path that must begin with the root '/' (or [A-Z]: under windows) // 3) mod The moderator. // // Returns: E_FORMAT Either the current path is not a valid and absolute path. Or the moderator is not valid. // E_OK The oparation was successful.
const char* i ; // For derivation of target dir const char* j ; // For derivation of target dir hzString tgt ; // Directory we should end up in uint32_t nPosn ; // Positions for manipulation of m_ServerDir
ult.Clear() ;
/* ** Check the current is an absolute path (must begin with '/') */
if (!mod) { ult = current ; if (!ult) ult = "/" ; return E_OK ; }
if (!current) { i = *mod ; if (*i != CHAR_FWSLASH) { ult = "invalid moderator" ; return E_FORMAT ; } ult = mod ; return E_OK ; }
i = *current ; if (*i != CHAR_FWSLASH) { ult = "invalid current path" ; return E_FORMAT ; }
/* ** Check the directory is reasonable */
i = *mod ; if (*i == CHAR_FWSLASH) ult = mod ; else { ult = current ;
if (mod == "..") { // truncate our record of the remote dir
j = *ult ; nPosn = CstrLast(j, CHAR_FWSLASH) ; if (nPosn) ult.Truncate(nPosn) ; }
if (!memcmp(i, "../", 3)) { for (;;) { if (memcmp(i, "../", 3)) break ; i += 3 ;
j = *ult ; nPosn = CstrLast(j, CHAR_FWSLASH) ; ult.Truncate(nPosn) ; } }
// Add the relative path ult += "/" ; ult += i ; }
return E_OK ; }
hzEcode hzFtpClient::_setmetafile (hzDirent& meta, char* line) { // Set file metadata (hzDirent) with a line from the server's directory listing. // // Arguments: 1) meta The hzDirent to be populated // 2) line The line from the directory listing // // Returns: E_FORMAT If the data is garbled // E_OK If the operation successful
_hzfunc("hzFtpClient::_setmetafile") ;
hzXDate now ; // Current date/time hzXDate date ; // Date of file (according to server) char* i ; // Line iterator char* pSze ; // Pointer to file size in line char* pMon ; // Pointer to month in line char* pDay ; // Pointer to day in line char* pTim ; // Pointer to time in line hzString test ; // Set to line uint32_t file_year ; // Year of file uint32_t file_month ; // Month of file uint32_t file_day ; // Day of file uint32_t file_hour ; // Hour of file uint32_t file_min ; // Minute of file hzEcode rc = E_OK ; // Return code
now.SysDateTime() ; test = line ;
i = line + 10 ; // Bypass user no, username, groupname - ignore whitespace for ( ; *i && *i <= CHAR_SPACE ; i++) ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ; for (i++ ; *i && *i <= CHAR_SPACE ; i++) ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ; for (i++ ; *i && *i <= CHAR_SPACE ; i++) ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ;
// Get size and date - ignore whitespace for (i++ ; *i && *i <= CHAR_SPACE ; i++) ; pSze = i ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ; for (i++ ; *i && *i <= CHAR_SPACE ; i++) ; pMon = i ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ; for (i++ ; *i && *i <= CHAR_SPACE ; i++) ; pDay = i ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ; for (i++ ; *i && *i <= CHAR_SPACE ; i++) ; pTim = i ; for (i++ ; *i > CHAR_SPACE ; i++) ; *i = 0 ;
meta.SetName(i + 1) ; meta.SetSize((uint32_t) atol(pSze)) ;
/* ** Get file date */
if (!strcmp(pMon, "Jan")) file_month = 1 ; else if (!strcmp(pMon, "Feb")) file_month = 2 ; else if (!strcmp(pMon, "Mar")) file_month = 3 ; else if (!strcmp(pMon, "Apr")) file_month = 4 ; else if (!strcmp(pMon, "May")) file_month = 5 ; else if (!strcmp(pMon, "Jun")) file_month = 6 ; else if (!strcmp(pMon, "Jul")) file_month = 7 ; else if (!strcmp(pMon, "Aug")) file_month = 8 ; else if (!strcmp(pMon, "Sep")) file_month = 9 ; else if (!strcmp(pMon, "Oct")) file_month = 10 ; else if (!strcmp(pMon, "Nov")) file_month = 11 ; else if (!strcmp(pMon, "Dec")) file_month = 12 ; else return hzerr(E_FORMAT, "Invalid month: Line [%s]", *test) ;
if (!IsPosint(file_day, pDay)) return hzerr(E_FORMAT, "Invalid day: Line [%s]", *test) ;
if (!IsPosint(file_hour, pTim)) return hzerr(E_FORMAT, "Invalid hour: Line [%s]", *test) ;
if (file_hour > 2000) { file_year = file_hour ; file_hour = 12 ; file_min = 0 ; } else { file_year = now.Year() ; if (!IsPosint(file_min, pTim + 3)) return hzerr(E_FORMAT, "Invalid minutes: Line [%s]", *test) ; }
// Do some logic to make up for the lack of year in date
rc = date.SetDate(file_year, file_month, file_day) ; if (rc != E_OK) { threadLog("Line [%s] Could not set date to %04d/%02d/%02d\n", *test, file_year, file_month, file_day) ; return E_FORMAT ; }
rc = date.SetTime(file_hour, file_min, 0) ; if (rc != E_OK) { threadLog("Line [%s] Could not set time to %02d:%02d\n", *test, file_hour, file_min) ; return E_FORMAT ; }
if (date.NoDays() > now.NoDays()) { date.SetDate(now.Year() - 1, file_month, file_day) ; if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Adjusted date that would be in the future if current year assumed - now set to %04d/%02d/%02d\n", date.Year(), file_month, file_day) ; }
meta.SetCtime(date) ; meta.SetMtime(date) ; return rc ; }
hzEcode hzFtpClient::_openpasv (hzTcpClient& X) { // Sends a PASV command to the FTP server and opens a data channel. Return values are as follows:- // // Arguments: 1) X The control channel client connection instance // // Returns: E_SENDFAIL The PASV command could not be sent. The calling function should attempt a reconnect. // E_RECVFAIL The PASV response was not received. The calling function should attempt a reconnect. // E_HOSTFAIL The data channel connection failed. The calling function should attempt a reconnect. // E_PROTOCOL The PASV response was not expected. The caller should abort the session as server is unlikely to respond as expected next time. // E_OK If the operation was successful. // // Scope: Private to the hzFtpClient class.
_hzfunc("hzFtpClient::_openpasv") ;
uint32_t nRecv ; // Bytes received uint32_t len ; // Length of outgoing msg uint32_t a ; // 1st part of IP addr for data connection uint32_t b ; // 2nd part of IP addr for data connection uint32_t c ; // 3rd part of IP addr for data connection uint32_t d ; // 4th part of IP addr for data connection uint32_t nA ; // First part of data port uint32_t nB ; // Second part of data port char* i ; // For processing prt nos etc hzEcode rc ; // Return code
/* ** Send PASV command */
sprintf(m_c_sbuf, "PASV\r\n") ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("PASV: Send failed\n") ; return rc ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv response to PASV command\n") ; return rc ; }
if (m_nRescode != 227) { threadLog("Expected code of 227: Got %s\n", m_c_rbuf) ; return E_PROTOCOL ; }
/* ** Convert recived message to a port number to connect to */
i = strchr(m_c_rbuf, '(') ; if (!i) { threadLog("Expected a substring of (...) containing data conn details\n") ; return E_PROTOCOL ; }
SplitCSV(m_Array, i + 1) ; //, 6) ; a = m_Array[0] ? atoi(*m_Array[0]) : 0 ; b = m_Array[1] ? atoi(*m_Array[1]) : 0 ; c = m_Array[2] ? atoi(*m_Array[2]) : 0 ; d = m_Array[3] ? atoi(*m_Array[3]) : 0 ; nA = m_Array[4] ? atoi(*m_Array[4]) : 0 ; nB = m_Array[5] ? atoi(*m_Array[5]) : 0 ;
m_nDataPort = (nA * 256) + nB ;
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Nos are %d:%d:%d:%d, %d,%d - Will connect for data on port %d\n", a, b, c, d, nA, nB, m_nDataPort) ;
/* ** Now set up data client */
rc = X.ConnectStd(m_Server, m_nDataPort) ; if (rc != E_OK) { threadLog("Could not conect to FTP data port\n") ; return rc ; }
X.SetSendTimeout(3) ; X.SetRecvTimeout(3) ;
return rc ; }
void hzFtpClient::_logrescode (void) { // Report FTP return codes to the logfile of the current thread // // Arguments: 1) fn Function name // // Returns: None
//_hzfunc("hzFtpClient::_logrescode") ;
switch (m_nRescode) { // 100 Codes: The requested action is being taken. Expect a reply before proceeding with a new command.
case 110: threadLog("110 - Restart marker reply\n") ; break ; case 120: threadLog("120 - Service ready in (n) minutes\n") ; break ; case 125: threadLog("125 - Data connection already open, transfer starting\n") ; break ; case 150: threadLog("150 - File status okay, about to open data connection\n") ; break ;
// 200 Codes: The requested action has been successfully completed.
case 200: threadLog("200 - Command okay\n") ; break ; case 202: threadLog("202 - Command not implemented\n") ; break ; case 211: threadLog("211 - System status, or system help reply\n") ; break ; case 212: threadLog("212 - Directory status\n") ; break ; case 213: threadLog("213 - File status\n") ; break ; case 214: threadLog("214 - Help message\n") ; break ; case 215: threadLog("215 - NAME system type (Official sys name from list in the Assigned Numbers document)\n") ; break ; case 220: threadLog("220 - Service ready for new user\n") ; break ; case 221: threadLog("221 - Service closing control connection (Logged out)\n") ; break ; case 225: threadLog("225 - Data connection open, no transfer in progress\n") ; break ; case 226: threadLog("226 - Closing data channel. Requested file action OK\n") ; break ; case 227: threadLog("227 - Entering Passive Mode\n") ; break ; case 230: threadLog("230 - User logged in, proceed\n") ; break ; case 250: threadLog("250 - Requested file action okay, completed\n") ; break ; case 257: threadLog("257 - Pathname created\n") ; break ;
// 300 Codes: The command has been accepted, but the requested action is being held pending receipt of further information.\n") ;
case 331: threadLog("331 - User name okay, need password\n") ; break ; case 332: threadLog("332 - Need account for login\n") ; break ; case 350: threadLog("350 - Requested file action pending further info\n") ; break ;
// 400 Codes: The command was not accepted and the requested action did not take place. The error condition is temporary, and the action may be requested again.
case 421: threadLog("421 - Service not available, closing control channel\n") ; break ; case 425: threadLog("425 - Can't open data connection\n") ; break ; case 426: threadLog("426 - Connection closed, transfer aborted\n") ; break ; case 450: threadLog("450 - File unavailable\n") ; break ; case 451: threadLog("451 - Local error in processing\n") ; break ; case 452: threadLog("452 - Insufficient storage space in system\n") ; break ;
// 500 Codes: The command was not accepted and the requested action did not take place.
case 500: threadLog("500 - Syntax error, command unrecognized\n") ; break ; case 501: threadLog("501 - Syntax error in parameters or arguments\n") ; break ; case 502: threadLog("502 - Command not implemented\n") ; break ; case 503: threadLog("503 - Bad sequence of commands\n") ; break ; case 504: threadLog("504 - Command not implemented for that parameter\n") ; break ; case 530: threadLog("530 - User not logged in\n") ; break ; case 532: threadLog("532 - Need account for storing files\n") ; break ; case 550: threadLog("550 - File unavailable (not found or no access)\n") ; break ; case 552: threadLog("552 - Storage allocation exceeded\n") ; break ; case 553: threadLog("553 - Illegal file name\n") ; break ;
default: threadLog("No valid command received\n") ; break ; } }
hzEcode hzFtpClient::_ftprecv (uint32_t& nRecv, const char* callFn) { // Recieve data from the FTP control channel // // Arguments: 1) nRecv Bytes received // 2) callFn Calling function // // Returns: E_RECVFAIL Connection breakdown // E_PROTOCOL Invalid response (Give up) // E_OK Operation successful
_hzfunc("hzFtpClient::_ftprecv") ;
hzChain msg ; // Recived FTP control message chain _ftpline fline ; // Line of FTP control message char* i ; // Line iterator uint32_t nTry ; // Reconnection retry count uint32_t code ; // FTP server return code hzEcode rc = E_OK ; // Return code
m_nRescode = 0 ;
if (!m_Stack.Count()) { // No stored response (from an earlier multi-line response) so we have to do a recv
for (nTry = 0 ; nTry < 10 ; nTry++) { rc = m_ConnControl.Recv(m_c_rbuf, nRecv, HZ_MAXPACKET) ;
if (rc == E_TIMEOUT) continue ;
if (rc != E_OK) threadLog("%s. Failed to recv from control channel (%d bytes, error=%s)\n", callFn, nRecv, Err2Txt(rc)) ; break ; }
if (nTry == 10) { threadLog("%s. Stalled - 10 counts and out\n", callFn) ; return E_RECVFAIL ; }
if (nTry) threadLog("%s. Stalled - %d counts and resumed\n", callFn, nTry) ;
if (rc != E_OK) { threadLog("%s. Failed error=%s\n", callFn, Err2Txt(rc)) ; return rc ; }
m_c_rbuf[nRecv] = 0 ;
// Separate the lines in the response. If there is more than one they need to be stacked. Then // subsequent calls to this function can take a line from the que.
i = m_c_rbuf ;
if (IsDigit(i[0]) && IsDigit(i[1]) && IsDigit(i[2])) fline.m_code = ((i[0] - '0') * 100) + ((i[1] - '0') * 10) + (i[2] - '0') ; else { threadLog("%s. Invalid server response: %s\n", callFn, i) ; return E_PROTOCOL ; }
for (;;) { if (*i == 0) { if (msg.Size()) { fline.m_msg = msg ; m_Stack.Add(fline) ; } break ; }
if (i[0] == CHAR_NL || (i[0] == CHAR_CR && i[1] == CHAR_NL)) { if (i[0] == CHAR_CR) *i++ = 0 ; *i++ = 0 ;
code = ((i[0] - '0') * 100) + ((i[1] - '0') * 10) + (i[2] - '0') ; if (code == fline.m_code) continue ;
if (msg.Size()) { fline.m_msg = msg ; m_Stack.Add(fline) ; msg.Clear() ; fline.m_msg.Clear() ; fline.m_code = code ; } continue ; }
msg.AddByte(*i) ; i++ ; }
if (m_Stack.Count() > 1) { // Check if these multiple lines are part of the same message (ie all have same code), if so the lines are merged.
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) { threadLog("%s. xple control lines (%d)\n", callFn, m_Stack.Count()) ; for (nTry = 0 ; nTry < m_Stack.Count() ; nTry++) { fline = m_Stack[nTry] ; threadLog(" - %d %s\n", fline.m_code, *fline.m_msg) ; } } } }
/* ** Process response (from server or stack) */
if (!m_Stack.Count()) { threadLog("%s. Empty message stack\n", callFn) ; return E_PROTOCOL ; }
fline = m_Stack[0] ; m_nRescode = fline.m_code ;
strcpy(m_c_rbuf, *fline.m_msg) ;
if (m_Stack.Count() == 1) m_Stack.Clear() ; else { rc = m_Stack.Delete(0) ; if (rc != E_OK) threadLog("%s. Corrupt m_Stack delete\n", callFn) ; }
if (rc != E_OK) threadLog("%s. _ftprecv error=%s\n", callFn, Err2Txt(rc)) ;
return rc ; }
hzEcode hzFtpClient::_reconnect (void) { // In the event of a disconnection, reconnect to server and restore the session in full. Note that this differs from the original connect call in that the // possible outcomes no longer scanarios like server not found. // // Arguments: None // // Returns: E_HOSTFAIL Reconnection unsuccessful breakdown // E_PROTOCOL Invalid response (Give up) // E_OK Operation successful
_hzfunc("hzFtpClient::_reconnect") ;
std::ofstream os ; // Out to dirlist file std::ifstream is ; // Read back dirlist file
hzChain CR ; // Receiver chain (control) hzChain XR ; // Receiver chain (data) hzEcode rc ; // Return code from publication functions
uint32_t nRecv ; // Bytes recv uint32_t len ; // Length of client request
threadLog("-> %s::%s\n", *m_Server, *m_ServerDir) ;
/* ** Connect to server and set socket timeouts */
m_ConnControl.Close() ;
for (; m_nTries < 10 ;) { sleep(1) ;
if ((rc = m_ConnControl.ConnectStd(m_Server, 21)) != E_OK) { if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Attempt %d: Could not conect to FTP server\n", m_nTries) ; m_nTries++ ; continue ; }
// Now reconnected, receive server hello if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Did not get server hello\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
if (m_nRescode != 220) { threadLog("Expected a 220, got %s\n", m_c_rbuf) ; rc = E_PROTOCOL ; break ; }
// Send username sprintf(m_c_sbuf, "USER %s\r\n", *m_Username) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Failed to send USER command\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
// Receive response if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Failed to receive respose to USER command\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
_logrescode() ;
if (m_nRescode >= 400) { threadLog("Aborting Session (case 1)\n") ; rc = E_PROTOCOL ; break ; }
// Send password sprintf(m_c_sbuf, "PASS %s\r\n", *m_Password) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Failed to send PASS command (password)\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
// Receive server response to password sleep(1) ;
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Failed to receive respose to password\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
_logrescode() ;
if (m_nRescode >= 400) { threadLog("Aborting Session (case 2)\n") ; rc = E_PROTOCOL ; m_ConnControl.Close() ; break ; }
// Now because we are re-connecting, we just want to go to the directory we were last in. Send CWD command. sprintf(m_c_sbuf, "CWD %s\r\n", *m_ServerDir) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send CWD command\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
// Receive the response if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Failed to receive CWD respose\n") ; m_ConnControl.Close() ; m_nTries++ ; continue ; }
_logrescode() ;
if (m_nRescode == 250) rc = E_OK ; else { rc = E_PROTOCOL ; m_ConnControl.Close() ; } break ; }
return rc ; }
hzEcode hzFtpClient::StartSession (void) { // Start an FTP session (establish a connection witht the FTP server) // // Arguments: None // // Returns: E_HOSTFAIL If the initial control channel connection could not be established. The usual remedy is to try again later. // E_SENDFAIL If either the username or password could not be sent. Broken pipe so try again later. // E_RECVFAIL If the expected reponses were not recieved. Again a broken pipe so try again later. // E_PROTOCOL If either the username or password was invalid. Fatal error as this must be sorted out before trying again. // E_OK If the operation was successful.
_hzfunc("hzFtpClient::StartSession") ;
std::ofstream os ; // Out to dirlist file std::ifstream is ; // Read back dirlist file
hzChain CR ; // Receiver chain (control) hzChain XR ; // Receiver chain (data) hzEcode rc ; // Return code from publication functions
uint32_t nRecv ; // Bytes recv uint32_t len ;
/* ** Connect to server and set socket timeouts */
for (m_nTries = 0 ; m_nTries < 5 ; m_nTries++) { if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Calling Connect for control channel at server %s\n", *m_Server) ;
rc = m_ConnControl.ConnectStd(m_Server, 21) ; if (rc != E_OK) { if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Attempt %d: Could not connect to FTP server control channel. (error=%s)\n", m_nTries, Err2Txt(rc)) ; continue ; } m_nTries = 0 ; break ; }
if (rc != E_OK) { threadLog("Given up!\n") ; return rc ; }
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Connected to control channel at server %s with sock %d on port 21\n", *m_Server, m_ConnControl.Sock()) ;
rc = m_ConnControl.SetSendTimeout(10) ; if (rc != E_OK) { threadLog("Could not set send timeout: Quitting\n") ; m_ConnControl.Close() ; return rc ; }
rc = m_ConnControl.SetRecvTimeout(10) ; if (rc != E_OK) { threadLog("Could not set send timeout: Quitting\n") ; m_ConnControl.Close() ; return rc ; }
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Send & Recv timeouts set\n") ;
/* ** Receive server hello */
rc = _ftprecv(nRecv, *_fn) ; if (rc != E_OK) { threadLog("Did not get server hello\n") ; m_ConnControl.Close() ; return rc ; }
if (m_nRescode != 220) { threadLog("Expected a 220, got %s\n", m_c_rbuf) ; m_ConnControl.Close() ; return E_PROTOCOL ; }
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Got server hello ....\n") ;
/* ** Send username */
sprintf(m_c_sbuf, "USER %s\r\n", *m_Username) ; len = strlen(m_c_sbuf) ;
rc = m_ConnControl.Send(m_c_sbuf, len) ; if (rc != E_OK) { threadLog("Failed to send USER command\n") ; m_ConnControl.Close() ; return rc ; }
rc = _ftprecv(nRecv, *_fn) ; if (rc != E_OK) { threadLog("Failed to receive respose to USER command\n") ; m_ConnControl.Close() ; return rc ; }
_logrescode() ;
if (m_nRescode != 331) { threadLog("Bad response to USER. Expected a 331, got %d. Presume username %s invalid\n", m_nRescode, *m_Username) ; QuitSession() ; rc = E_PROTOCOL ; return rc ; }
/* ** Send password */
sprintf(m_c_sbuf, "PASS %s\r\n", *m_Password) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("PASS: Send failed\n") ; m_ConnControl.Close() ; return rc ; }
sleep(1) ;
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv response to PASS command\n") ; m_ConnControl.Close() ; return rc ; }
_logrescode() ;
if (m_nRescode >= 400) { threadLog("Aborting Session\n") ; QuitSession() ; rc = E_PROTOCOL ; return rc ; }
rc = GetServerDir() ; if (rc != E_OK) { threadLog("Could not get server directory\n") ; return rc ; }
threadLog("Logged in to %s as %s\n", *m_Server, *m_Username) ; m_nTries = 0 ;
return rc ; }
hzEcode hzFtpClient::GetServerDir (void) { // Obtain the current working directory on the server // // Arguments: None // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_PROTOCOL If the server did not respond as expected // E_OK If the current working directory on the server was obtained
_hzfunc("hzFtpClient::GetServerDir") ;
char* i ; // Start of pathname (in quotes) char* j ; // End of pathname (in quotes) uint32_t nRecv ; // Bytes received uint32_t len ; // Outgoing msg length uint32_t nTry ; // Connection retries hzEcode rc ; // Standard return code
/* ** Send PWD command and receive resposns, reconnect if required. */
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** Send the PWD command, recv response */
sprintf(m_c_sbuf, "PWD\r\n") ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send PWD command\n") ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv response to PWD command\n") ; continue ; }
break ; }
if (rc == E_OK) { /* ** Check respons is valid */
_logrescode() ;
if (m_nRescode != 257) { threadLog("Expected a 257 return code, got %d\n", m_nRescode) ; rc = E_PROTOCOL ; } }
if (rc == E_OK) { /* ** Set our record of the current server directory */
for (i = m_c_rbuf ; *i && *i != CHAR_DQUOTE ; i++) if (*i) { i++ ; for (j = i ; *j && *j != CHAR_DQUOTE ; j++) ; *j = 0 ; }
if (m_ServerDir != i) m_ServerDir = i ; m_nTries = 0 ;
return E_OK ; }
threadLog("Could not get server directory\n") ; return rc ; }
hzEcode hzFtpClient::SetLocalDir (const hzString& dir) { // Set the working directory on the local machine // // Arguments: 1) dir The taget local directory // // Returns: E_NOTFOUND If the local directory does not exist // E_TYPE If the specified local directory is not a directory // E_OK If the local directory is set
FSTAT fs ; // Local file status
if (lstat(*dir, &fs) == -1) return E_NOTFOUND ;
if (!ISDIR(fs.st_mode)) return E_TYPE ;
m_LocalDir = dir ;
/* if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("hzFtpClient::SetLocalDir. Set local directory to %s\n", *m_LocalDir) ; */
return E_OK ; }
hzEcode hzFtpClient::SetRemoteDir (const hzString& dir) { // Changes the directory on the server to either an absolute or relative path. This also sets the member m_ServerDir which tracks the server directory. The // protocol is simple. The client sends a CWD with the supplied argument. The server either returns a 250 if the directory was changed or it returns a 550 // if there is no such directory. // // Argument: dir The taget remote directory // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_NOTFOUND If the requested directory does not exist on the server or does not permit the client to access it // E_OK If the remote working directory was set
_hzfunc("hzFtpClient::SetRemoteDir") ;
hzString tgt ; // Directory we should end up in uint32_t nRecv ; // Length of server response uint32_t len ; // Length of client request uint32_t nTry ; // Connection retries hzEcode rc ; // Return code
/* ** Check the directory is reasonable */
rc = _checkpath(tgt, m_ServerDir, dir) ; if (rc != E_OK) { threadLog("In dir %s, acting on CD %s results in %s\n", *m_ServerDir, *dir, *tgt) ; return rc ; }
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Tgt dir is %s\n", *tgt) ;
/* ** Send CWD command and receive resposns, reconnect if required. */
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** Send the CWD command, recv response */
sprintf(m_c_sbuf, "CWD %s\r\n", *dir) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send CWD command - Reconnecting ...\n") ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv CWD response - Reconnecting ...\n") ; continue ; }
break ; }
if (rc == E_OK) { /* ** Check respons is valid */
if (m_nRescode == 250) rc = E_OK ; else rc = E_NOTFOUND ; }
if (rc == E_OK) { m_ServerDir = tgt ;
if (_hzGlobal_Debug & HZ_DEBUG_CLIENT) threadLog("Remote dir is now %s\n", *m_ServerDir) ; m_nTries = 0 ; return E_OK ; }
if (rc == E_NOTFOUND) { threadLog("Could not CD to %s (%s)\n", *tgt, m_c_rbuf) ; return rc ; }
threadLog("Failure report (err=%s)\n", Err2Txt(rc)) ; return rc ; }
hzEcode hzFtpClient::RemoteDirCreate (const hzString& dir) { // Create a directory on the server // // Argument: dir The taget remote directory // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_WRITEFAIL If the requested server directory was not created // E_OK If the remote working directory was created
_hzfunc("hzFtpClient::RemoteDirCreate") ;
uint32_t nRecv ; // Incoming message length uint32_t len ; // Outgoing message length uint32_t nTry ; // Reconections hzEcode rc ; // Return code
/* ** Send MKD command and receive resposns, reconnect if required. */
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** Send the CWD command */
sprintf(m_c_sbuf, "MKD %s\r\n", *dir) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send MKD command\n") ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv MKD response\n") ; continue ; }
break ; }
/* ** Check response code */
_logrescode() ;
if (m_nRescode != 257) { threadLog("Expected a 257 - Got %s\n", m_c_rbuf) ; return E_WRITEFAIL ; }
return E_OK ; }
hzEcode hzFtpClient::RemoteDirDelete (const hzString& SvrDirname) { // Delete a directory on the server // // Arguments: 1) dir The taget remote directory // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_WRITEFAIL If the requested server directory was not deleted // E_OK If the remote working directory was deleted
_hzfunc("hzFtpClient::RemoteDirDelete") ;
uint32_t nRecv ; // Incoming message length uint32_t len ; // Outgoing message length uint32_t nTry ; // Reconections hzEcode rc ; // Return code
/* ** Send RMD command and receive resposns, reconnect if required. */
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** Send the RMD command */
sprintf(m_c_sbuf, "RMD %s\r\n", *SvrDirname) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send RMD command\n") ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv response to RMD command\n") ; continue ; }
break ; }
/* ** Check response code */
if (m_nRescode != 257) { threadLog("Could not delete directory\n") ; return E_NOTFOUND ; }
return E_OK ; }
hzEcode hzFtpClient::GetDirList (hzVect<hzDirent>& listing, const hzString& Criteria) { // Get a directory listing filtered by search criteria // // Arguments: 1) listing The vector of directory entries populated by this operation // 2) Criteria The file/directory search criteria // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_PROTOCOL If the server did not provide a listing // E_OK If the directory listing was recieved
_hzfunc("hzFtpClient::GetDirList") ;
hzTcpClient X ; // Temporary data connection hzDirent meta ; // Metafile hzChain Listing ; // Temporary store for listing hzChain::Iter z ; // For processing naked listing hzString filename ; // Name of file uint32_t nRecv ; // Bytes recieved this packet uint32_t nTotal ; // Total bytes recieved uint32_t len ; // Length of content to send uint32_t nTry ; // Reconnections uint32_t nTryData ; // Timeouts on the data channel char* j ; // For loading line buffer char cvLine [300] ; // Line buffer hzEcode rc = E_OK ; // Return code
/* ** Loop round until success */
// X.SetDebug(_hzGlobal_Debug & HZ_DEBUG_CLIENT) ;
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { X.Close() ; m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
// First step is to open a data channel rc = _openpasv(X) ; if (rc != E_OK) { threadLog("Failed PASV .. aborting\n") ; continue ; }
// Send the LIST command if (Criteria) sprintf(m_c_sbuf, "LIST %s\r\n", *Criteria) ; else sprintf(m_c_sbuf, "LIST\r\n") ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send LIST command\n") ; continue ; }
// Get the expected 150 in the control client if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Did not recieve a response to LIST command\n") ; continue ; }
if (m_nRescode != 150) { threadLog("Expected code of 150 but got %d. Aborting\n", m_nRescode) ; _logrescode() ; rc = E_PROTOCOL ; break ; }
// Get the listing in the data client nTotal = 0 ; Listing.Clear() ;
for (nTryData = 0 ; nTryData < 3 ;) { rc = X.Recv(m_x_rbuf, nRecv, HZ_MAXPACKET) ; if (rc != E_OK) { threadLog("LIST: Read failed (total so far %d bytes)\n", nTotal) ; break ; }
if (!nRecv) break ; nTotal += nRecv ; m_x_rbuf[nRecv] = 0 ;
Listing << m_x_rbuf ; }
X.Close() ;
// Unless the transfer is a complete success, do a reconnect. if (rc == E_TIMEOUT || nTryData == 3) continue ; if (rc == E_RECVFAIL) continue ;
if (Listing.Size()) threadLog("LIST: Total listing %d bytes\n", nTotal) ; else threadLog("LIST: Empty list. No files\n") ;
// If a list has been sent there will be a 226 sent by the server which we will need to read - if only to get rid of it before we move on. if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Did not recieve a response to LIST command\n") ; continue ; }
if (m_nRescode != 226) { threadLog("LIST: Expected code of 226 (Xfer complete) but got %d. Aborting\n", m_nRescode) ; rc = E_PROTOCOL ; }
// No further communication required so drop out break ; }
if (rc == E_OK) { // Examine the dir listing (if there is one) if (!Listing.Size()) return rc ;
j = cvLine ; for (z = Listing ; !z.eof() ; z++) { if (*z == CHAR_CR) continue ;
if (*z == CHAR_NL) { // Process the line *j = 0 ; j = cvLine ;
// Set meta data for the file rc = _setmetafile(meta, cvLine) ; if (rc != E_OK) break ;
// Add the meta data rc = listing.Add(meta) ; if (rc != E_OK) break ; continue ; }
*j++ = *z ; }
m_nTries = 0 ; return rc ; }
threadLog("Could not get directory listing\n\n") ; return rc ; }
hzEcode hzFtpClient::FileDownload (hzDirent& finfo) { // Downloads a file from the FTP server. This is a two-step process of firstly sending of a PASV command to open the data channel // and secondly the download over the data channel. The data channel is closed after the download so this function is called for // every file required. // // Arguments: 1) finfo The hzDirent for the server-side file. This will have been obtained by a prior directory listing. // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_NOTFOUND The file on the server does not exist. // E_OPENFAIL The target file on the local machine could not be opened. Presumed fatal // E_WRITEFAIL The target file on the local machine could not be written too. Presumed fatal. // E_OK If the operation successful
_hzfunc("hzFtpClient::FileDownload") ;
struct utimbuf svr_mtime ; // Used to alter date on files to match server version
std::ofstream os ; // Target file stream hzTcpClient X ; // Data channel hzString tmpFile ; // Download to a temporary file first, rename upon success hzString tgtFile ; // Download to a temporary file first, rename upon success uint32_t epoch ; // Time in seconds since 1970 uint32_t nRecv ; // Incoming message size uint32_t nTotal = 0 ; // Total bytes downloaded uint32_t len ; // Outgoing message length uint32_t nTry ; // Local limit on reconnects hzEcode rc ; // Return code
// Elimintate zero size files if (!finfo.Size()) return E_OK ;
// X.SetDebug(_hzGlobal_Debug & HZ_DEBUG_CLIENT) ;
tmpFile = m_LocalDir + "/" ; tmpFile += finfo.strName() ; tmpFile += ".tmp" ;
tgtFile = m_LocalDir + "/" + finfo.strName() ;
/* ** Loop round until success */
for (nTry = 0 ; nTry < 2 ; nTry++) { start: nTotal = 0 ;
if (nTry == 1) { X.Close() ; m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** First step is to open a data channel */
rc = _openpasv(X) ; if (rc != E_OK) { threadLog("Failed PASV\n") ; continue ; }
/* ** Send the RETR command and recv response */
sprintf(m_c_sbuf, "RETR %s\r\n", finfo.txtName()) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send RETR command to get file %s\n", finfo.txtName()) ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not get RETR respeonse (file=%s)\n", finfo.txtName()) ; continue ; }
/* ** Check response code */
if (m_nRescode >= 400) { threadLog("Got bad response to RETR (%d)\n", m_nRescode) ; rc = E_PROTOCOL ; break ; }
/* ** Then if all is well, download the file */
os.open(*tmpFile) ; if (os.fail()) { threadLog("Failed to open download temp file %s\n", *tmpFile) ; rc = E_OPENFAIL ; break ; }
for (nTotal = 0 ; nTotal < finfo.Size() ;) { rc = X.Recv(m_x_rbuf, nRecv, HZ_MAXPACKET) ; if (rc != E_OK) { threadLog("Socket error during download of file %s\n", finfo.txtName()) ; X.Close() ; os.close() ; os.clear() ; goto start ; }
if (!nRecv) break ; nTotal += nRecv ;
os.write(m_x_rbuf, nRecv) ; if (os.fail()) { threadLog("Failed to write data to the target file %s\n", finfo.txtName()) ; rc = E_WRITEFAIL ; break ; } }
X.Close() ; os.close() ;
if (rc == E_WRITEFAIL) { threadLog("Aborted operation to download %d of %d bytes to the target file %s - No disk space\n", nTotal, finfo.Size(), finfo.txtName()) ; }
// Initial comms phase over so exit loop break ; }
if (rc == E_OK) { /* ** Rename the file to the target name, set mtime of the local file to match that of the server */
rename(*tmpFile, *tgtFile) ; threadLog("Downloaded %d of %d bytes to the target file %s\n", nTotal, finfo.Size(), finfo.txtName()) ;
epoch = finfo.Mtime() ; svr_mtime.actime = time(0) ; svr_mtime.modtime = epoch ; utime(finfo.txtName(), &svr_mtime) ;
/* ** Get server progress report of download. If broken pipe reconnect but as we have the file already we don't ** have to repeat any steps. */
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not get response to RETR command (file=%s)\n", finfo.txtName()) ; rc = _reconnect() ; } else { if (m_nRescode != 226) threadLog("Expected code of 226 (Xfer complete), got %d\n", m_nRescode) ;
if (m_nRescode >= 400) rc = E_RECVFAIL ; } }
if (rc != E_OK) threadLog("Could not download %s\n\n", finfo.txtName()) ; else { threadLog("Downloaded %s\n", finfo.txtName()) ; m_nTries = 0 ; }
return rc ; }
hzEcode hzFtpClient::FileUpload (const hzString& SvrFilename, const hzString& LocFilename) { // Uploads a file to the FTP server. This involves both the sending of a PASV command to open the data channel and the upload over the data // channel. The data channel is closed after the upload so this function is called for every file required. // // Arguments: 1) SvrFilename Name file is to be called on the server // 2) LocFilename Name file has on local machine // // Returns: E_NOTFOUND Named file does not exists. // E_OPENFAIL Named file could not be opened. // E_PROTOCOL Protocol error. // E_OK Operation successful, file fully uploaded.
_hzfunc("hzFtpClient::FileDownload") ;
hzTcpClient X ; // TCP client FSTAT fs ; // File info ifstream is ; // Target file stream uint32_t nSize ; // Total bytes to upload (file size) uint32_t nRecv ; // Bytes in server response uint32_t nDone = 0 ; // Bytes uploaded uint32_t len ; // Command length uint32_t nTry ; // Reconnections hzEcode rc ; // Return code
/* ** Because the source file is local, first thing we do is check it exists and the size */
// X.SetDebug(_hzGlobal_Debug & HZ_DEBUG_CLIENT) ;
if (lstat(*LocFilename, &fs) == -1) { threadLog("Failed to locate source file for upload %s\n", *LocFilename) ; return E_NOTFOUND ; } nSize = fs.st_size ;
rc = OpenInputStrm(is, *LocFilename) ; if (rc != E_OK) return rc ;
/* ** Now upload the file: Loop round until success */
for (nTry = 0 ; nTry < 2 ; nTry++) { nDone = 0 ;
if (nTry) { X.Close() ; m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
// Next step is to open a data channel rc = _openpasv(X) ; if (rc != E_OK) { threadLog("Failed PASV ... trying again\n") ; continue ; }
// Send the STOR command and recv the response sprintf(m_c_sbuf, "STOR %s\r\n", *SvrFilename) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send STOR command to upload file %s (attempt %d of 3)\n", *SvrFilename, nTry) ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv STOR response (file=%s, attempt %d of 3)\n", *SvrFilename, nTry) ; rc = _reconnect() ; continue ; }
// Check response code if (m_nRescode >= 400) { threadLog("Got bad STOR response (%d) - aborting\n", m_nRescode) ; rc = E_PROTOCOL ; break ; }
// Then if all is well, upload the file for (nDone = 0 ; nDone < nSize && rc == E_OK ; nDone += is.gcount()) { is.read(m_x_sbuf, HZ_MAXPACKET) ; if (!is.gcount()) break ;
rc = X.Send(m_x_sbuf, is.gcount()) ; if (rc != E_OK) threadLog("No socket during upload of file %s to %s\n", *LocFilename, *SvrFilename) ; }
// If not OK then retry if (rc != E_OK) continue ;
// Otherwise close channel and exit loop is.close() ; X.Close() ; break ; }
// Get server progress report of upload if (rc == E_OK) { if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) threadLog("Could not get progress report to STOR command. Giving up (file=%s)\n", *SvrFilename) ; else { if (m_nRescode != 226) { threadLog("Expected code of 226 (Xfer complete), got %d\n", m_nRescode) ; rc = E_PROTOCOL ; } } }
if (nDone != nSize) { threadLog("File size of %d bytes, uploaded %d\n", nSize, nDone) ; if (rc == E_OK) rc = E_SENDFAIL ; }
if (rc != E_OK) threadLog("Could not upload %s\n\n", *LocFilename) ; else { threadLog("Uploaded %s\n", *LocFilename) ; m_nTries = 0 ; }
return rc ; }
hzEcode hzFtpClient::FileDelete (const hzString& SvrFilename) { // Delete a file on the server // // Arguments: 1) SvrFilename Name file has on the server // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_WRITEFAIL If the remote file was not deleted // E_OK If the remote file was deleted
_hzfunc("hzFtpClient::FileDelete") ;
uint32_t nRecv ; // Length of server response uint32_t len ; // Length of client request uint32_t nTry ; // Reconnection retry count hzEcode rc ; // Return code
/* ** Send DELE command and receive resposns, reconnect if required. */
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** Send the DELE command and check the response */
sprintf(m_c_sbuf, "DELE %s\r\n", *SvrFilename) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send DELE command (for file %)\n", *SvrFilename) ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not recv DELE response (for file %)\n", *SvrFilename) ; continue ; }
break ; }
/* ** Check server response */
if (m_nRescode >= 400) { threadLog("Got bad response (%d) to DELE %s\n", m_nRescode, *SvrFilename) ; return E_NODATA ; }
return E_OK ; }
hzEcode hzFtpClient::FileRename (const hzString& oldsvrname, const hzString& newsvrname) { // Rename a file on the server // // Arguments: 1) oldsvrname Old name of file/directory on server // 2) newname New name of file/directory on server // // Returns: E_HOSTFAIL If there was a communication failure and reconnect failed // E_WRITEFAIL If the requested server file was not renamed // E_OK If the remote file was renamed
_hzfunc("hzFtpClient::FileRename") ;
uint32_t nRecv ; // Length of server response uint32_t len ; // Length of client request uint32_t nTry ; // Reconnection retry count hzEcode rc ; // Return code
/* ** Send RNFR & RNTO commands and receive resposns, reconnect if required. */
for (nTry = 0 ; nTry < 2 ; nTry++) { if (nTry == 1) { m_ConnControl.Close() ; rc = _reconnect() ; if (rc != E_OK) break ; }
/* ** Send the RNFR (rename from) command and check the response */
//sprintf(m_c_sbuf, "RENAME %s %s\r\n", *oldsvrname, *newsvrname) ; sprintf(m_c_sbuf, "RNFR %s\r\n", *oldsvrname) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send RNFR command (file %s to %s)\n", *oldsvrname, *newsvrname) ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not get RNFR response (file %s to %s)\n", *oldsvrname, *newsvrname) ; continue ; }
if (m_nRescode == 350) { /* ** The RNFR was OK so now send the RNTO */
sprintf(m_c_sbuf, "RNTO %s\r\n", *newsvrname) ; len = strlen(m_c_sbuf) ;
if ((rc = m_ConnControl.Send(m_c_sbuf, len)) != E_OK) { threadLog("Could not send RNTO command (file %s to %s)\n", *oldsvrname, *newsvrname) ; continue ; }
if ((rc = _ftprecv(nRecv, *_fn)) != E_OK) { threadLog("Could not get RNTO response (file %s to %s)\n", *oldsvrname, *newsvrname) ; continue ; } }
break ; }
/* ** Check server response */
if (m_nRescode >= 400) { threadLog("Got bad response (%d) to RNFR/RNTO command (file %s to %s)\n", m_nRescode, *oldsvrname, *newsvrname) ; return E_NODATA ; }
return E_OK ; }
hzEcode hzFtpClient::QuitSession (void) { // Sends a QUIT command to the FTP server to end the session // // Arguments: None // // Returns: E_SENDFAIL If the command could not be sent // E_RECVFAIL If the server failed to acknowledge the command // E_OK If the operation was succussful
_hzfunc("hzFtpClient::QuitSession") ;
uint32_t nRecv ; // Length of server response uint32_t len ; // Length of client request hzEcode rc ; // Return code
threadLog("hzFtpClient::QuitSession. Quitting session: - ") ;
sprintf(m_c_sbuf, "QUIT\r\n") ; len = strlen(m_c_sbuf) ;
rc = m_ConnControl.Send(m_c_sbuf, len) ; if (rc == E_TIMEOUT) threadLog("Timed out whilst sending QUIT command\n") ; else if (rc != E_OK) threadLog("Could not send QUIT command\n") ; else { rc = m_ConnControl.Recv(m_c_sbuf, nRecv, 1024) ;
if (rc == E_TIMEOUT) threadLog("Timed out whilst awaiting QUIT response\n") ; else if (rc != E_OK) threadLog("Could not recv QUIT response\n") ; else threadLog("FTP Quit Successful\n") ; }
m_ConnControl.Close() ; return E_OK ; }
/* ** Section 2: Management of mass downloads from a known FTP host */
hzEcode hzFtpHost::_hz_FtpDnhist::Load (hzMapS<hzString,hzDirent>& dlist, hzString& opdir) { // If no history file create one, open it for writing and return. If there is read it in and then open it for appending. // // Arguments: 1) dlist Directory listing // 2) opdir Operational directory // // Returns: E_ARGUMENT If the operational directory is not supplied // E_OPENFAIL If the history file cannot be opened for reading // E_OK If the history file has been created afresh or read in
FSTAT fs ; // Local file status ifstream is ; // Input stream hzDirent de ; // Directory entry to be inserted hzString hname ; // Name of history file hzString fname ; // Name of downloaded file uint32_t epoch ; // Recorded file epoch uint32_t size ; // Recorded file size
hname = opdir + "/histdn.dat" ;
if (stat(*hname, &fs) == -1) { _file = fopen(*hname, "a") ; return E_OK ; }
is.open(*hname) ; if (is.fail()) return E_OPENFAIL ;
for (;;) { is.getline(buf, 255) ; if (!is.gcount()) break ;
buf[10] = buf[21] = 0 ; IsPosint(epoch, buf) ; IsPosint(size, buf + 11) ; fname = buf + 22 ;
de.InitNorm(0, fname, -1, size, epoch, epoch, 0777, 0, 0, 1) ; dlist.Insert(fname, de) ; }
is.close() ;
_file = fopen(*hname, "a") ; return E_OK ; }
hzEcode hzFtpHost::_hz_FtpDnhist::Append (uint32_t epoch, uint32_t size, const hzString& name) { // Append the download history with file data // // Arguments: 1) epoch Epoch time // 2) size Size of file // 3) name Filename // // Returns: E_ARGUMENT If the filename is not supplied // E_WRITEFAIL If the file to be appeneded has not been opened // E_OK If the history file was appended
if (!name) return E_ARGUMENT ; if (!_file) return E_WRITEFAIL ;
fprintf(_file, "%010u %010d %s\n", epoch, size, *name) ; return E_OK ; }
hzEcode hzFtpHost::Init ( const hzString& host, const hzString& user, const hzString& pass, const hzString& remDir, const hzString& locDir, const hzString& criteria ) { // Initialize an FTP host or account. This means setting up all the parameters for connecting to an FTP server // // Arguments: 1) host The host name // 2) user The username for the account // 3) pass The password to the account // 4) remDir The opening directory on the server (usually /) // 5) locDir The opening directory on the local machine // 6) criteria Opening search criteria for listing (if any) // // Returns: E_ARGUMENT If either the hostname, username, password or local directory is not supplied // E_NOCREATE If the local directory does not exist and could not be created // E_OK If the operation was successful
_hzfunc("hzFtpSite::Initialize") ;
hzEcode rc = E_OK ; // Return code
/* ** Check we have an action, that the action is associated with a publication and that it has an issue date */
m_Host = host ; m_Username = user ; m_Password = pass ; m_Source = remDir ; m_Repos = locDir ; m_Criteria = criteria ;
if (!host || !user || !pass || !locDir) { if (!host) threadLog("No FTP host supplied\n") ; if (!user) threadLog("No FTP username supplied\n") ; if (!pass) threadLog("No FTP password supplied\n") ; if (!locDir) threadLog("No FTP local directory supplied\n") ;
return E_ARGUMENT ; }
/* ** Formulate directory to collect files from and assert that directory */
rc = AssertDir(*m_Repos, 0755) ; if (rc != E_OK) return hzwarn(rc, "Failed to assert local dir (%s)\n", *m_Repos) ;
m_bInit = true ; return rc ; }
hzEcode hzFtpHost::GetAll (void) { // Connect to FTP Server with username and password. Go to remote pre-determined directory and obtain listing according to given criteria. For each file in the // listing, check if we do not already have the (complete) file from a previous run. If not, download it to the target directory and then record this action. // // Arguments: None // // Returns: E_HOSTFAIL If the collection is cut off and cannot be re-established within an acceptable period. // E_PROTOCOL If the action fails for reasons of protocol. // E_OK If the collection runs to completion
_hzfunc("hzFtpSite::GetAll") ;
hzMapS <hzString,hzDirent> mapFilesCurr ; // Files already downloaded for the current issue (in name order) hzVect <hzDirent> vecFilesCurr ; // Files already downloaded for the current issue
hzVect <hzDirent> listing ; // The list of files
hzFtpClient ftp ; // The FTP client connection hzDirent de ; // Directory entry hzDirent de_a ; // Directory entry of file we have already (if applicable)
hzString localfile ; // Target file hzString rento ; // Rename server file to ... uint32_t nIndex ; // Directory entry iterator uint32_t nAlready = 0 ; // When a file is skipped (it is in the listing for the criteria but we already have it), we // increment this value.
uint32_t nDnloads = 0 ; // Number of files downloaded (this attempt).
uint32_t nListed = 0 ; // Set from the count of server files meeting the criteria. At the end we add up nAlready and // nDnloads and only when these add up to nListed can the status be set to ACTION_COLLECTED // otherwise status should be ACTION_STARTED
int32_t sys_rc ; // Return from system call char* cmd ; // For system call command when needed hzEcode rc = E_OK ; // Return from FTP functions
/* ** Check we have everything we need: The publication and the olddata and newdata dirs */
if (!m_bInit) Fatal("Permenant Failure: No FTP Parameters\n") ;
threadLog("Collecting from server [%s,%s,(%s)] (user=%s, pass=%s) to dir %s ->", *m_Host, *m_Source, *m_Criteria, *m_Username, *m_Password, *m_Repos) ; threadLog("OK GO!\n") ;
// Get a combined listing of the historic and current data input directories dnh.Load(mapFilesCurr, m_Repos) ;
rc = ReadDir(vecFilesCurr, *m_Repos) ; if (rc != E_OK) { threadLog("Failed to read local dir (%s)\n", *m_Repos) ; return rc ; }
for (nIndex = 0 ; nIndex < vecFilesCurr.Count() ; nIndex++) { de = vecFilesCurr[nIndex] ; mapFilesCurr.Insert(de.strName(), de) ; }
// Set up FTP params rc = ftp.Initialize(m_Host, m_Username, m_Password) ; if (rc != E_OK) { threadLog("Could not INIT FTP session to server [%s] (user=%s, pass=%s). Error is %s\n", *m_Host, *m_Username, *m_Password, Err2Txt(rc)) ; return rc ; }
// Start FTP session rc = ftp.StartSession() ; if (rc != E_OK) { threadLog("Could not start FTP session to server [%s] (user=%s, pass=%s). Error is %s\n", *m_Host, *m_Username, *m_Password, Err2Txt(rc)) ; return rc ; }
// Go to the relevent remote directory rc = ftp.SetRemoteDir(m_Source) ; if (rc != E_OK) { if (rc == E_NOTFOUND) { threadLog("Permenant Failure: Expected source directory %s has not been found on the server\n", *m_Source) ; ftp.QuitSession() ; return rc ; }
threadLog("Temporary error: Could not CD to %s\n", *m_Source) ; return rc ; }
rc = ftp.SetLocalDir(m_Repos) ; if (rc != E_OK) { threadLog("Permenant Failure: Expected target directory %s has not been found or is otherwise in error on the server\n", *m_Repos) ; ftp.QuitSession() ; return rc ; }
// Get the server directory listing rc = ftp.GetDirList(listing, m_Criteria) ; if (rc != E_OK) { if (rc == E_PROTOCOL) { threadLog("Permanent error: Could not get a listing. Protocol error\n") ; return rc ; }
threadLog("We have %d files in server listing but an error of %s - will rerun\n", listing.Count(), Err2Txt(rc)) ; return rc ; }
// Got listing but is there anything in it if (listing.Count() == 0) { // Expected files were not there so try again later threadLog("Temporary error: Got directory listing but there were no files matching [%s]\n", *m_Criteria) ; ftp.QuitSession() ; return rc ; }
// Compare files and download new ones nListed = listing.Count() ;
for (nIndex = 0 ; nIndex < listing.Count() ; nIndex++) { de = listing[nIndex] ;
if (!mapFilesCurr.Exists(de.strName())) threadLog("Downloading file (%d of %d) %s\n", nIndex, listing.Count(), de.txtName()) ; else { de_a = mapFilesCurr[de.strName()] ;
if (de_a.Size() == de.Size()) { if (de_a.Mtime() >= de.Mtime()) { threadLog("Skiping file (%d of %d) %s\n", nIndex, listing.Count(), de.txtName()) ; nAlready++ ; continue ; } }
threadLog("Re-fetching file (%d of %d) %s\n", nIndex, listing.Count(), de.txtName()) ; }
if (!de.strName()) { threadLog("Critical error: An Un-named meta file has been found in the listing. Corruption suspected\n") ; ftp.QuitSession() ; return rc ; }
if (!de.Size()) continue ;
// We have a new file so fetch localfile = m_Repos + "/" + de.strName() ;
rc = ftp.FileDownload(de) ; if (rc != E_OK) { if (rc == E_OPENFAIL || rc == E_WRITEFAIL) { threadLog("Problem opening/writing file (%s) (error=%s)\n", de.txtName(), Err2Txt(rc)) ; return rc ; }
ftp.QuitSession() ; return rc ; }
// The file is OK - Add it to download history dnh.Append(de.Mtime(), de.Size(), de.strName()) ; nDnloads++ ;
// Deal with case where file is zipped if (strstr(de.txtName(), ".zip")) { cmd = new char[300] ;
sprintf(cmd, "/usr/bin/unzip -q -j -n -d %s %s", *m_Repos, *localfile) ; sys_rc = system(cmd) ;
if (sys_rc) { threadLog("System call [%s] returns %d\n", cmd, sys_rc) ; delete cmd ; return rc ; }
threadLog("System call [%s] returns OK\n", cmd) ; unlink(*localfile) ; delete cmd ; } }
// Quit FTP session, return to original dir and finish. ftp.QuitSession() ;
if ((nDnloads + nAlready) == nListed) { rc = ReadDir(vecFilesCurr, *m_Repos) ; if (rc != E_OK) { threadLog("Failed to read target dir (%s) after download - delaying for 15 mins\n", *m_Repos) ; return rc ; } else { hzXDate now ; // System date and time uint32_t eHi ; // Latest file found uint32_t eIs ; // System date and time as epoch
now.SysDateTime() ; eIs = now.AsEpoch() ; eHi = 0 ;
for (nIndex = 0 ; nIndex < vecFilesCurr.Count() ; nIndex++) { de = vecFilesCurr[nIndex] ;
if (eHi < de.Mtime()) eHi = de.Mtime() ; }
if (eHi > (eIs - 600)) threadLog("Oldest file less than 10 mins old - delaying just in case\n") ; } } else { threadLog("Total of %d listed on server. We have %d already and %d were downloaded. %d files remain outstanding\n", nListed, nAlready, nDnloads, (nListed - nAlready - nDnloads)) ; }
return rc ; }