I'm trying to reproduce a problem where when some of our clients can receive a wrong file contents while downloading with SFTP protocol.
Based on our SFTP server (CrushFTP) logs they probably open multiple files in one session and then use some pipelining to download the files. I don't know what kind of library they use as they are using some SAAS provider for this.
I'm trying to reproduce the behavior with libssh2, but I'm getting sftp_read() internal error
when calling libssh2_sftp_read asynchronously on the second open file after the first one returned LIBSSH2_ERROR_EAGAIN - even on localhost connection to OpenSSH.
While browsing SSH File Transfer Protocol IETF Draft I can see that the protocol allows for opening multiple files simultaneously and requesting their contents with multiple SSH_FXP_READ request without waiting for response.
Below is the code I use for testing (SSCCE, but quite long - C is quite verbose) - compiled with gcc sftp_multifile.c -lssh2 -Wall -g -o sftp_multifile
and tested with:
./sftp_multifile localhost 22 testusername testpassword /usr/share/dict/words /usr/share/doc/words/readme.txt
connect try: ai_family=10 ai_socktype=1 ai_protocol=6 addr=::1 port=22
opening: /usr/share/dict/words
opening: /usr/share/doc/words/readme.txt
reading: /usr/share/dict/words from 10134400 to 140736927200896
read result: -37
reading: /usr/share/doc/words/readme.txt from 10135536 to 140736927201920
read result: -31
Bad read result: -31
Read error: /usr/share/doc/words/readme.txt: sftp_read() internal error
Did I make an error in my code or does libssh2 just not support pipelining libssh2_sftp_read for multiple open files or maybe it's just a bug in libssh2 that should be reported to its maintainer?
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <arpa/inet.h>
#define CHUNK_SIZE (1024)
int sftp_connect(char const* hostname, char const* port)
{
struct addrinfo* addrinfo_result, *addrinfo_current;
int sfd, s;
struct addrinfo addrinfo_hints = {0};
addrinfo_hints.ai_family = AF_UNSPEC;
addrinfo_hints.ai_socktype = SOCK_STREAM;
s = getaddrinfo(hostname, port, &addrinfo_hints, &addrinfo_result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
for (addrinfo_current = addrinfo_result; addrinfo_current != NULL; addrinfo_current = addrinfo_current->ai_next) {
char s[INET6_ADDRSTRLEN];
sfd = socket(addrinfo_current->ai_family, addrinfo_current->ai_socktype, addrinfo_current->ai_protocol);
if (sfd == -1) continue;
{
void* addr;
in_port_t port;
switch(addrinfo_current->ai_family) {
case AF_INET: {
struct sockaddr_in* sockaddr = (struct sockaddr_in*)addrinfo_current->ai_addr;
addr = &(sockaddr->sin_addr);
port = ntohs(sockaddr->sin_port);
break;
}
case AF_INET6: {
struct sockaddr_in6* sockaddr = (struct sockaddr_in6*)addrinfo_current->ai_addr;
addr = &(sockaddr->sin6_addr);
port = ntohs(sockaddr->sin6_port);
break;
}
default:
fprintf(stderr, "unknown family: %d\n", addrinfo_current->ai_family);
exit(EXIT_FAILURE);
}
inet_ntop(addrinfo_current->ai_family, addr, s, INET6_ADDRSTRLEN);
fprintf(stderr, "connect try: ai_family=%d ai_socktype=%d ai_protocol=%d addr=%s port=%d\n", addrinfo_current->ai_family, addrinfo_current->ai_socktype, addrinfo_current->ai_protocol, s, port);
}
if (connect(sfd, addrinfo_current->ai_addr, addrinfo_current->ai_addrlen) != 0) {
fprintf(stderr, "connect: %s\n", strerror(errno));
close(sfd);
continue;
}
break;
}
if (addrinfo_current == NULL) {
fprintf(stderr, "connection failed\n");
exit(EXIT_FAILURE);
}
freeaddrinfo(addrinfo_result);
return sfd;
}
void retrieve_files(int socket, LIBSSH2_SESSION* session, LIBSSH2_SFTP *sftp_session, int filec, char *filev[])
{
LIBSSH2_SFTP_HANDLE* sftp_handles[filec];
int finished[filec];
int toread = filec;
for(int i=0; i<filec; i++) {
fprintf(stderr, "opening: %s\n", filev[i]);
sftp_handles[i] = libssh2_sftp_open(sftp_session, filev[i], LIBSSH2_FXF_READ, 0);
if (!sftp_handles[i]) {
char* errmsg;
libssh2_session_last_error(session, &errmsg, NULL, 0);
fprintf(stderr, "Failure opening remote file: %s: %s\n", filev[i], errmsg);
exit(EXIT_FAILURE);
}
finished[i]=0;
}
char buffer[CHUNK_SIZE];
libssh2_session_set_blocking(session, 0);
while (toread) {
for(int i=0; i<filec; i++) {
if (finished[i]) {
continue;
} else {
int read_result;
do {
fprintf(stderr, "reading: %s from %ld to %ld\n", filev[i], (long)sftp_handles[i], (long)(buffer+CHUNK_SIZE*i));
read_result = libssh2_sftp_read(sftp_handles[i], buffer, CHUNK_SIZE);
fprintf(stderr, "read result: %d\n", read_result);
if (read_result > 0) {
printf("%s: ", filev[i]);
fwrite(buffer, sizeof(char), read_result, stdout);
printf("\n");
} else {
break;
}
} while (1);
if (read_result == LIBSSH2_ERROR_EAGAIN) {
continue;
} else if (read_result == 0) {
finished[i] = 1;
toread--;
} else {
fprintf(stderr, "Bad read result: %d\n", read_result);
char* errmsg;
libssh2_session_last_error(session, &errmsg, NULL, 0);
fprintf(stderr, "Read error: %s: %s\n", filev[i], errmsg);
exit(EXIT_FAILURE);
}
}
}
if (toread) {
struct pollfd fds[1] = {0};
fds[0].fd = socket;
fds[0].events = POLLIN;
poll(fds, 1, 3000);
}
}
}
int main(int argc, char *argv[])
{
LIBSSH2_SESSION *session;
LIBSSH2_SFTP *sftp_session;
int rc, sock;
if (argc<6) {
fprintf(stderr, "Usage: %s hostname port username password file1 [file2] [file3...]\n", argv[0]);
return EXIT_FAILURE;
}
sock = sftp_connect(argv[1], argv[2]);
session = libssh2_session_init();
if (!session) {
return EXIT_FAILURE;
}
rc = libssh2_session_handshake(session, sock);
if (rc) {
char* errmsg;
libssh2_session_last_error(session, &errmsg, NULL, 0);
fprintf(stderr, "Failure establishing SSH session: %s\n", errmsg);
return EXIT_FAILURE;
}
rc = libssh2_userauth_password(session, argv[3], argv[4]);
if (rc) {
char* errmsg;
libssh2_session_last_error(session, &errmsg, NULL, 0);
fprintf(stderr, "%s\n", errmsg);
return EXIT_FAILURE;
}
sftp_session = libssh2_sftp_init(session);
if (sftp_session == NULL) {
char* errmsg;
libssh2_session_last_error(session, &errmsg, NULL, 0);
fprintf(stderr, "Unable to init SFTP session: %s\n", errmsg);
return EXIT_FAILURE;
}
retrieve_files(sock, session, sftp_session, argc-5, argv+5);
libssh2_session_set_blocking(session, 1);
libssh2_sftp_shutdown(sftp_session);
libssh2_session_disconnect(session, "");
libssh2_session_free(session);
libssh2_exit();
return EXIT_SUCCESS;
}
connect: Connection refused
-- perhaps some service(s) need to be running on localhost? Maybe something more, like preparing a test user with testusername and testpassword? It would be helpful if you could list all steps needed to run your test case, to arrive at the "internal error" problem. – Outhaul