How to copy a file in C/C++ with libssh and SFTP - c++

I want to copy a file from a client to a remote server, but I don't understand how to do it with the libssh library SFTP API.
The situation is that: The SSH session is open and the SFTP session is open too, I can create a file and write in it from the client to the server with the integrated function of libssh.
I did not find an easy way to copy a file from the client to the server with a simple function like sftp_transfer(sourceFile(like c:\my document\hello world.txt),RemoteFile(/home/user/hello world.txt),right(read and write)) ?
With what I have understood from the tutorial it is first creating a file in the remote location (server) then it is opening this file with this line of code:
file = sftp_open(sftp, "/home/helloworld.txt",access_type,1);
After that the file is created on the server, and then it is writing into this created file with a buffer:
const char *helloworld = "Hello, World!\n";
int length = strlen(helloworld);
nwritten = sftp_write(file, helloworld, length);
My question is now if I have a file for example a .doc file and I want to transfer/upload that file from c:\mydocument\document.doc to remote the remote server /home/user/document.doc, how can I do it with this method ?
How can I put this file into the sftp_write() function to send it like the helloworld in the sample function?
I may be not good enough in programming to understand, but I really tried to understand it and I'm stuck with it.
Thanks in advance for your help
See below a sample of the code I used to test:
// Set variable for the communication
char buffer[256];
unsigned int nbytes;
//create a file to send by SFTP
int access_type = O_WRONLY | O_CREAT | O_TRUNC;
const char *helloworld = "Hello, World!\n";
int length = strlen(helloworld);
//Open a SFTP session
sftp = sftp_new(my_ssh_session);
if (sftp == NULL)
{
fprintf(stderr, "Error allocating SFTP session: %s\n",
ssh_get_error(my_ssh_session));
return SSH_ERROR;
}
// Initialize the SFTP session
rc = sftp_init(sftp);
if (rc != SSH_OK)
{
fprintf(stderr, "Error initializing SFTP session: %s.\n",
sftp_get_error(sftp));
sftp_free(sftp);
return rc;
}
//Open the file into the remote side
file = sftp_open(sftp, "/home/helloworld.txt",access_type,1);
if (file == NULL)
{
fprintf(stderr, "Can't open file for writing: %s\n",ssh_get_error(my_ssh_session));
return SSH_ERROR;
}
//Write the file created with what's into the buffer
nwritten = sftp_write(file, helloworld, length);
if (nwritten != length)
{
fprintf(stderr, "Can't write data to file: %s\n",
ssh_get_error(my_ssh_session));
sftp_close(file);
return SSH_ERROR;
}

Open the file in the usual way (using C++'s fstream or C's stdio.h), read its contents to a buffer, and pass the buffer to sftp_write.
Something like this:
ifstream fin("file.doc", ios::binary);
if (fin) {
fin.seekg(0, ios::end);
ios::pos_type bufsize = fin.tellg(); // get file size in bytes
fin.seekg(0); // rewind to beginning of file
std::vector<char> buf(bufsize); // allocate buffer
fin.read(buf.data(), bufsize); // read file contents into buffer
sftp_write(file, buf.data(), bufsize); // write buffer to remote file
}
Note that this is a very simple implementation. You should probably open the remote file in append mode, then write the data in chunks instead of sending single huge blob of data.

The following example uses ifstream in a loop to avoid loading a whole file into a memory (what the accepted answer is doing):
ifstream fin("C:\\myfile.zip", ios::binary);
while (fin)
{
constexpr size_t max_xfer_buf_size = 10240
char buffer[max_xfer_buf_size];
fin.read(buffer, sizeof(buffer));
if (fin.gcount() > 0)
{
ssize_t nwritten = sftp_write(NULL, buffer, fin.gcount());
if (nwritten != fin.gcount())
{
fprintf(
stderr, "Error writing to file: %s\n", ssh_get_error(ssh_session));
sftp_close(file);
return 1;
}
}
}

I used followed the example here.
sftp_read_sync uses an infinite loop to read a file from a server into /path/to/profile
from server path /etc/profile.

Related

How to scp(transfer) file by a libssh?

I am trying to transfer compiled executable file (compiled code c++, that prints 'Hello World!') to openssh linux server by using library libssh(I cant use terminal scp of openssh). I know, that libssh has a scp functions to transfer file from and to remoted ssh server. However there is some errors. This is a code of scp:
void ext(const char*c){
std::cout<<c;
exit(-5);
}
int scp_do(ssh_session session);
int scp_copy(ssh_session session, ssh_scp scp);
int main(){
int p = 22;
ssh_session session = ssh_new();
if (session == NULL)
exit(-1);
ssh_options_set(session, SSH_OPTIONS_HOST, "192.168.0.106");
ssh_options_set(session, SSH_OPTIONS_PORT, &p);
ssh_options_set(session, SSH_OPTIONS_USER, "user");
int rc = ssh_connect(session);
if (rc != SSH_OK){ //if not connected - exit
ssh_disconnect(session);
ext("Error connecting");
}
rc = ssh_userauth_password(session, NULL, "passwd");
if(rc != SSH_AUTH_SUCCESS){ //if not authed - exit
ssh_disconnect(session);
ssh_free(session);
return 0;
}
scp_do(session); //copying file, main task of this code
ssh_disconnect(session);
ssh_free(session);
return 0 ;
}
int scp_do(ssh_session session){ //copying file, main task of this code
ssh_scp scp;
int rc;
scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, ".");
if (scp == NULL)
ext("Error allocating scp session");
rc = ssh_scp_init(scp);
if (rc != SSH_OK)
ext("Error initializing scp session");
char buffer[1024];
std::ifstream fin("hello"); // compiled executable file('hello world'), that I want to sent to server
rc = ssh_scp_push_file(scp, "hello", 1024, S_IRUSR | S_IWUSR); //creating server's file to copy there
if (rc != SSH_OK)
ext("Can't open remote file");
fin.get(buffer, 1024); //reading from client's file
rc = ssh_scp_write(scp, buffer, 1024); //copying all compiled code to server file
if (rc != SSH_OK)
ext("Can't write to remote file");
ssh_scp_close(scp);
ssh_scp_free(scp);
return SSH_OK;
}
When I am trying to execute a transferred file on server, Firstly, I discover it is not
executable. So I am doing chmod +x for file. Secondly, When i am executing file, I am getting error:
[1] 6867 segmentation fault ./hello
However, when I am using scp of openssh to transfer file, there's no errors and it's executable. What can I do to solve my problems?
The size of file is like
4 62 17168 hello
I was thinking it will be less 1024 bytes, bcs it's a just hello world). So, i need only to send 17168 bytes.

Transferring Sqlite DB file using libcurl functions but unable to open db file immediately to send the data to database

I am running a C++ application which communicates with a PLC based machine.
PLC based machine creates a .db(SQLITE) file once the machine processes the parts.
I wrote a function to transfer the sqlite db from PLC machine to PC using libcurl functions.
After the transfer is complete, i need to loop through the db file and transfer the data to the company network database table.
I have an issue with looping through the file once the transfer is done.
The file when reading throws an exception "(11) SQLITE_CORRUPT" - "Disk Image is Malformed".
But once i close my c++ application and open again, the last sqlite db file is able to open and loop through.
I am attaching the code below, let me know if there is a way to make the db file available to read once transferred through libcurl library. Should i be using some extra commands to make it available.
Information of the ftp server.
char dbfile[128];
char ofile[512];
//Pulling all the information about the ftp server information from parsed.xml -> Request/DataBlock/LogFile/Host, Path, File
char *host = tagTextValue(doc, "REQUEST.DATABLOCK.LOGFILE.HOST");// -- 172.16.1.100
if (host)
{
if (char *path = tagTextValue(doc, "REQUEST.DATABLOCK.LOGFILE.PATH")) // -- /pub/export/mdt
{
if (char *file= tagTextValue(doc, "REQUEST.DATABLOCK.LOGFILE.FILE"))// -- 20200226_004636_90180.db
{
sprintf_s( dbfile, sizeof(dbfile), "ftp://%s/%s/%s", host, path, file );//Host Address
sprintf_s(ofile, sizeof(ofile), "c:\\xxxxx\\xxxxx\\%s", file);//Destination Address
}
}
}
///////////Start of FTP///////////////////
struct FtpFile
{
const char *filename;
FILE *stream;
};
FtpFile ftp =
{
ofile, /* name to store the file as if successful */
NULL
};
CURL *url = curl_easy_init();
curl_easy_setopt(url, CURLOPT_URL, dbfile);// dbfile has the complete path where to pull the file from.
FILE *ffp = fopen(ofile, "w"); // ofile mentions the destination on the receiving end
curl_easy_setopt(url, CURLOPT_WRITEFUNCTION, my_fwrite);
curl_easy_setopt(url, CURLOPT_WRITEDATA, &ftp);
curl_easy_setopt(url, CURLOPT_USERPWD, "ftpuser:xxxx");// Passing the login details to FTP server to pull the information from the 'server/machine'
CURLcode res = curl_easy_perform(url);
fclose(ffp);
curl_easy_cleanup(url);
////////////////End of FTP/////////////////////////
sqlite3 *db;
sqlite3_stmt *stmt;
char sql[1024];
Sleep(20000);
WireBondDataUploadType rec = rec2;
const char* sql1 = "select xxxxxxxxxx";
bool storeSuccessful = false;
const char *ofile2 = "c:\\xxxxx\\xxxxxxx\\20200310_021605_20039.db";
int rc;
rc = sqlite3_open(ofile, &db); //ST Opening a new Database connection
if (rc == SQLITE_OK)
{
rc=sqlite3_prepare(db, sql1, -1, &stmt, NULL);
if(rc != 0)
{
sqlite3_close(db);
}
else if(rc == SQLITE_OK)
{
//Process the information by reading the db file.
xxxxxxxx
xxxxxxxx
}
}
The solution to the problem was we forgot to the close the "opened" ftp file(ofile) in the above mentioned code.
Reference link - [https://curl.haxx.se/libcurl/c/ftpget.html][1]

Copying a file from a server to client using Libssh: issues with assigning file copy destination path

I want to copy a file from a server to client,i have already connected the server and i can add some content in a particular file in the server. I have gone through this tutorials and this ,hence i wrote some codes based on the tutorials. but its not working. I knew that there is some error while getting the destination path.while debugging i got this error Unhandled exception thrown: read access violation. file was nullptr... and i found 'The error is in fd (sftp_file fd;), i assigned the path (client) "C:/Users/Sami/Desktop/" where i want to copy the wi.exe from "/home/server/Desktop/sa/wi.exe" this path (server).' How can i correct this?
access_type = O_RDONLY;
file = sftp_open(sftp, "/home/server/Desktop/sa/wi.exe",access_type,0);
fd = sftp_open(sftp,"C:/Users/Sami/Desktop/", O_CREAT, 0);
nbytes = sftp_read(file, buffer, sizeof(buffer));
nwritten = sftp_write(fd, buffer, nbytes);
sftp_close(file);
You should only use sftp_open, sftp_read and sftp_write for files which are on the remote computer. For files on the local computer just use the normal file functions and classes (e.g. fopen or fstream).
E.g.
access_type = O_RDONLY;
file = sftp_open(sftp, "/home/server/Desktop/sa/wi.exe",access_type,0);
FILE* fd = fopen("C:/Users/Sami/Desktop/wi.exe", "w");
nbytes = sftp_read(file, buffer, sizeof(buffer));
nwritten = fwrite(buffer, sizeof(char), nbytes, fd);
sftp_close(file);
fclose(fd);
Also you missed the file name on your destination path, as πάντα ῥεῖ commented.

FTP server file transfer

I am uncertain about a few things regarding ftp file transfer. I am writing an ftp server and I am trying to figure out how to make the file tranfer work correctly. So far it works somehow but I have certain doubts. Here is my file transfer function (only retrieve so far):
void RETRCommand(int & clie_sock, int & c_data_sock, char buffer[]){
ifstream file; //clie_sock is used for commands and c_data_sock for data transfer
char *file_name, packet[PACKET_SIZE]; //packet size is 2040
int packet_len, pre_pos = 0, file_end;
file_name = new char[strlen(buffer + 5)];
strcpy(file_name, buffer + 5);
sprintf(buffer, "150 Opening BINARY mode data connection for file transfer\r\n");
if (send(clie_sock, buffer, strlen(buffer), 0) == -1) {
perror("Error while writing ");
close(clie_sock);
exit(1);
}
cout << "sent: " << buffer << endl;
file_name[strlen(file_name) - 2] = '\0';
file.open(file_name, ios::in | ios::binary);
if (file.is_open()) {
file.seekg(0, file.end);
file_end = (int) file.tellg();
file.seekg(0, file.beg);
while(file.good()){
pre_pos = file.tellg();
file.read(packet, PACKET_SIZE);
if ((int) file.tellg() == -1)
packet_len = file_end - pre_pos;
else
packet_len = PACKET_SIZE;
if (send(c_data_sock, packet, packet_len, 0) == -1) {
perror("Error while writing ");
close(clie_sock);
exit(1);
}
cout << "sent some data" << endl;
}
}
else {
sprintf(buffer, "550 Requested action not taken. File unavailable\r\n", packet);
if (send(clie_sock, buffer, packet_len + 2, 0) == -1) {
perror("Error while writing ");
close(clie_sock);
exit(1);
}
cout << "sent: " << buffer << endl;
delete(file_name);
return;
}
sprintf(buffer, "226 Transfer complete\r\n");
if (send(clie_sock, buffer, strlen(buffer), 0) == -1) {
perror("Error while writing ");
close(clie_sock);
exit(1);
}
cout << "sent: " << buffer << endl;
close(c_data_sock);
delete(file_name);
}
So one problem is the data transfer itself. I am not exactly sure how it is supposed to work. Now it works like this: the server sends all the data to c_data_sock, closes this socket and then the client starts doing something. Shouldn't the client recieve the data while the server is sending them? And the other problem is the abor command. How am I supposed to recieve the abor command? I tried recv with flag set to MSG_OOB but then I get an error saying "Invalid argument". I would be glad if someone could give me a hint or an example of how to do it right as I don't seem to be able to figure it out myself.
Thanks,
John
Ftp use two connections. First - is command connection, in your case it is clie_sock. 'ABOR' command should be received though it. You going to receive it the same way you received 'RETR' command.
To receive file client establishes data connection with your server ( c_data_sock socket ). It will not be opened till client connects, so this is the answer to your second question. You cannot start client after server executes this function. First client sends 'retr' command to your command socket. Then your sever waits new connection from client ( after sending him data ip and port ). Then client connects ( now you have your c_data_sock ready ) and sends all the data to that socket, which are in turn received by the client.
You probably need to read more about networking in general if you feel you don't understand it. I prefer this one: http://beej.us/guide/bgnet/
Also you have a memory leak here, after you allocate an array with the
file_name = new char[strlen(buffer + 5)];
you need to delete it using
delete [] file_name;
Otherwise file_name will be treated as a simple pointer, not an array, so most of array memory will be kept by your application which is bad especially when creating server.

Why am I getting a bad file descriptor error?

I am trying to write a short program that acts like a client, like telnet.
I receive from the user input like so: www.google.com 80 (the port number) and /index.html
However, I get some errors. When I write some debug information, it says that I have a bad file descriptor and Read Failed on file descriptor 100 messagesize = 0.
struct hostent * pHostInfo;
struct sockaddr_in Address;
long nHostAddress;
char pBuffer[BUFFER_SIZE];
unsigned nReadAmount;
int nHostPort = atoi(port);
vector<char *> headerLines;
char buffer[MAX_MSG_SZ];
char contentType[MAX_MSG_SZ];
int hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == SOCKET_ERROR)
{
cout << "\nCould Not Make a Socket!\n" << endl;
return 1;
}
pHostInfo = gethostbyname(strHostName);
memcpy(&nHostAddress,pHostInfo -> h_addr, pHostInfo -> h_length);
Address.sin_addr.s_addr = nHostAddress;
Address.sin_port=htons(nHostPort);
Address.sin_family = AF_INET;
if (connect(hSocket, (struct sockaddr *)&Address, sizeof(Address)) == SOCKET_ERROR)
{
cout << "Could not connect to the host!" << endl;
exit(1);
}
char get[] = "GET ";
char http[] = " HTTP/1.0\r\nHost: ";
char end[] = "\r\n\r\n";
strcat(get, URI);
strcat(get, http);
strcat(get, strHostName);
strcat(get, end);
strcpy(pBuffer, get);
int len = strlen(pBuffer);
send(hSocket, pBuffer, len, 0);
nReadAmount = recv(hSocket,pBuffer, BUFFER_SIZE, 0);
//Parsing of data here, then close socket
if (close(hSocket) == SOCKET_ERROR)
{
cout << "Could not close the socket!" << endl;
exit(1);
}
Thanks!
You have a buffer overrun in your code. You're attempting to allocate a bunch of character arrays together into a buffer than is only 5 bytes long:
char get[] = "GET "; // 'get' is 5 bytes long
char http[] = " HTTP/1.0\r\nHost: ";
char end[] = "\r\n\r\n";
strcat(get, URI); // BOOM!
strcat(get, http);
strcat(get, strHostName);
strcat(get, end);
This is likely overwriting your local variable hSocket, resulting in the "bad file descriptor error".
Since you're using C++ (you have a vector in your code), just use a std::string instead of C arrays of characters so that you don't have to worry about memory management.
Another cause of 'Bad file descriptor' can be encountered when trying to write into a location onto which you do not have write permission. For example, when trying to download content with wget in Windows environment:
Cannot write to 'ftp.org.com/parent/child/index.html' (Bad file descriptor).
Another case may be the target out file name includes characters the filesystem won't allow. For instance, suppose you are running wget command on a Linux computer and the target path is an external hard drive formatted with NTFS. The Linux Ext4 filesystem will allow for characters such as '?', but the NTFS filesystem will not. The default behavior for wget is to name the target folder as the specified url. If the url contains a special character like ':', NTFS will not accept that character, resulting in the error "Bad file descriptor". You can disable this default behavior in wget with the --no-host-directories parameter.
Another cause of 'bad file descriptor' can be accidentally trying to write() to a read-only descriptor. I was stuck on that for a while because I was spending all my time looking for a second close() or buffer overrun.