Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Howto implement SFTP with Qt/QNetworkAccessManager (C++)

I'm new to Qt and I would like to implement FTP and SFTP support for my software. As I googled I discovered that there doesn't exist a sftp library for Qt but it should be possible with QNetworkAccessManager. I tried then to discover on how to build a custom protocol or something like that but didn't figure out how to do it.

Does someone know how I could do that?

Thanks, Michael

like image 560
Michael Weibel Avatar asked Jul 20 '11 06:07

Michael Weibel


4 Answers

There is no support for SFTP in Qt SDK but Qt Creator implements SFTP.

I have isolated the library that contains SSH and SFTP and I have created a new project named QSsh in Github. The aim of the project is to provide SSH and SFTP support for any Qt Application.

I have written an example on how to upload a file using SFTP. Take a look at examples/SecureUploader/

I hope it might be helpful

like image 77
Paglian Avatar answered Nov 12 '22 12:11

Paglian


I do this using libssh. Very straight forward. https://api.libssh.org/stable/libssh_tutor_sftp.html

Don't forget to add your sftp server into known hosts in your system.

ssh-keyscan -H mysftpserver.com >> ~/.ssh/known_hosts

Example code:

#include "sftpuploader.h"
#include <QtDebug>
#include <QFileInfo>
#include <libssh/libssh.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libssh/sftp.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <QFile>

int verify_knownhost(ssh_session session)
{
    int state, hlen;
    unsigned char *hash = NULL;
    char *hexa;
    char buf[10];

    state = ssh_is_server_known(session);

    hlen = ssh_get_pubkey_hash(session, &hash);
    if (hlen < 0)
        return -1;

    switch (state)
    {
    case SSH_SERVER_KNOWN_OK:
        break; /* ok */

    case SSH_SERVER_KNOWN_CHANGED:
        fprintf(stderr, "Host key for server changed: it is now:\n");
        ssh_print_hexa("Public key hash", hash, hlen);
        fprintf(stderr, "For security reasons, connection will be stopped\n");
        free(hash);
        return -1;

    case SSH_SERVER_FOUND_OTHER:
        fprintf(stderr, "The host key for this server was not found but an other"
                        "type of key exists.\n");
        fprintf(stderr, "An attacker might change the default server key to"
                        "confuse your client into thinking the key does not exist\n");
        free(hash);
        return -1;

    case SSH_SERVER_FILE_NOT_FOUND:
        fprintf(stderr, "Could not find known host file.\n");
        fprintf(stderr, "If you accept the host key here, the file will be"
                        "automatically created.\n");
        /* fallback to SSH_SERVER_NOT_KNOWN behavior */

    case SSH_SERVER_NOT_KNOWN:
        hexa = ssh_get_hexa(hash, hlen);
        fprintf(stderr,"The server is unknown. Do you trust the host key?\n");
        fprintf(stderr, "Public key hash: %s\n", hexa);
        free(hexa);
        if (fgets(buf, sizeof(buf), stdin) == NULL)
        {
            free(hash);
            return -1;
        }
        if (strncasecmp(buf, "yes", 3) != 0)
        {
            free(hash);
            return -1;
        }
        if (ssh_write_knownhost(session) < 0)
        {
            fprintf(stderr, "Error %s\n", strerror(errno));
            free(hash);
            return -1;
        }
        break;

    case SSH_SERVER_ERROR:
        fprintf(stderr, "Error %s", ssh_get_error(session));
        free(hash);
        return -1;
    }

    free(hash);
    return 0;
}

bool upload(const QString &localFile,
                          const QString &dest,
                          const QString &host,
                          const QString &username,
                          const QString &passwd)
{
    bool retVal = false;

    QFileInfo info(localFile);

    m_localFilename = info.canonicalFilePath();
    m_remoteFilename = dest + "/" + info.fileName();

    int verbosity = SSH_LOG_PROTOCOL;
    int port = 22;
    int rc;
    sftp_session sftp;
    sftp_file file;
    int access_type;
    int nwritten;
    QByteArray dataToWrite;
    ssh_session my_ssh_session;

    QFile myfile(m_localFilename);

    if(!myfile.exists())
    {
        qDebug() << "SFTPUploader: File doesn't exist " << m_localFilename;
        return retVal;
    }

    my_ssh_session = ssh_new();
    if(my_ssh_session == NULL)
    {
        return retVal;
    }

    ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, host.toUtf8());
    ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
    ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port);

    rc = ssh_connect(my_ssh_session);
    if (rc != SSH_OK)
    {
        qDebug() << "SFTPUploader: Error connecting to localhost: " << ssh_get_error(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }
    else
    {
        qDebug() << "SFTPUploader: SSH connected";
    }

    // Verify the server's identity
    // For the source code of verify_knowhost(), check previous example
    if (verify_knownhost(my_ssh_session) < 0)
    {
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        qDebug() << "SFTPUploader: verify_knownhost failed";
        return retVal;
    }

    rc = ssh_userauth_password(my_ssh_session, username.toUtf8(), passwd.toUtf8());
    if (rc != SSH_AUTH_SUCCESS)
    {
        qDebug() << "SFTPUploader: Error authenticating with password: " << ssh_get_error(my_ssh_session);
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }
    else
    {
        qDebug() << "SFTPUploader: Authentication sucess";
    }

    sftp = sftp_new(my_ssh_session);
    if (sftp == NULL)
    {
        qDebug() << "SFTPUploader: Error allocating SFTP session:" << ssh_get_error(my_ssh_session);
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }

    rc = sftp_init(sftp);
    if (rc != SSH_OK)
    {
        qDebug() << "SFTPUploader: Error initializing SFTP session:", sftp_get_error(sftp);
        sftp_free(sftp);
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }

    access_type = O_WRONLY | O_CREAT | O_TRUNC;
    file = sftp_open(sftp, dest.toUtf8(), access_type, S_IRWXU);
    if (file == NULL)
    {
        qDebug() << "SFTPUploader: Can't open file for writing:", ssh_get_error(my_ssh_session);
        sftp_free(sftp);
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }

    if(myfile.open(QFile::ReadOnly))
    {
        dataToWrite = myfile.readAll();
    }

    nwritten = sftp_write(file, dataToWrite, dataToWrite.size());
    if (nwritten != dataToWrite.size())
    {
        qDebug() << "SFTPUploader: Can't write data to file: ", ssh_get_error(my_ssh_session);
        sftp_close(file);
        sftp_free(sftp);
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }

    rc = sftp_close(file);
    if (rc != SSH_OK)
    {
        qDebug() << "SFTPUploader: Can't close the written file:" << ssh_get_error(my_ssh_session);
        sftp_free(sftp);
        ssh_disconnect(my_ssh_session);
        ssh_free(my_ssh_session);
        return retVal;
    }
    else
    {
        qDebug() << "SFTPUploader: Success";
        retVal = true;
    }
    return retVal;
}

like image 20
Winston Rodrigues Avatar answered Nov 12 '22 12:11

Winston Rodrigues


You need a custom implementation for each protocol. But we can create a class like QHttp which will do that. There are several protocols that has similar semantic, but not all. So, if you want write it, tell me and I help you.

like image 1
Stan Sage Avatar answered Nov 12 '22 12:11

Stan Sage


There's no current SSH wrapper implementation in the Qt SDK. You have 3 choices here:

  1. Roll your own custom SSH/SFTP client implementation using the IETF RFC and standard drafts like RFC4253. This might not be what you're looking for.
  2. Use any of the ssh implementation libraries like openssh/libssh directly or writing your own Qt/C++ wrapper for future-reuse. Any reasonably decent project with ssh needs usually links to a an already established ssh library and uses it programatically. Like Qt Creator does, if you dig inside it long enough you'll find what user Paglian mentioned earlier. Relying on a library is less risky and more future-proof then rolling your own.
  3. Use openssh tooling at the command line interface directly, using QProcess just like you'd use it at the shell. This is is the fastest method if you're working on a proof-of-concept project and don't need any complex ftp operations, as it might get a bit difficult to devise a robust wrapper around the CLI tooling.
like image 1
Shinnok Avatar answered Nov 12 '22 11:11

Shinnok