Документация
ОС Аврора 5.0.1

saslclient.cpp

В приведённом ниже коде показано, как создать SASL-клиент.

/*
 Copyright (C) 2003-2008  Justin Karneges <justin@affinix.com>
 Copyright (C) 2006  Michail Pishchagin
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
 
#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include <cstdio>
 
// QtCrypto содержит объявления всех сущностей QCA.
#include <QtCrypto>
 
#ifdef QT_STATICPLUGIN
#include "import_plugins.h"
#endif
 
static QString prompt(const QString &s)
{
    printf("* %s ", qPrintable(s));
    fflush(stdout);
    char line[256];
    fgets(line, 255, stdin);
    QString result = QString::fromLatin1(line);
    if (result[result.length() - 1] == QLatin1Char('\n'))
        result.truncate(result.length() - 1);
    return result;
}
 
static QString socketErrorToString(QAbstractSocket::SocketError x)
{
    QString s;
    switch (x) {
    case QAbstractSocket::ConnectionRefusedError:
        s = QStringLiteral("в соединении отказано или истекло время ожидания");
        break;
    case QAbstractSocket::RemoteHostClosedError:
        s = QStringLiteral("удалённый хост закрыл соединение");
        break;
    case QAbstractSocket::HostNotFoundError:
        s = QStringLiteral("хост не найден");
        break;
    case QAbstractSocket::SocketAccessError:
        s = QStringLiteral("ошибка доступа");
        break;
    case QAbstractSocket::SocketResourceError:
        s = QStringLiteral("слишком много сокетов");
        break;
    case QAbstractSocket::SocketTimeoutError:
        s = QStringLiteral("время ожидания операции истекло");
        break;
    case QAbstractSocket::DatagramTooLargeError:
        s = QStringLiteral("размер датаграммы превышает системный предел");
        break;
    case QAbstractSocket::NetworkError:
        s = QStringLiteral("ошибка сети");
        break;
    case QAbstractSocket::AddressInUseError:
        s = QStringLiteral("адрес уже используется");
        break;
    case QAbstractSocket::SocketAddressNotAvailableError:
        s = QStringLiteral("адрес не принадлежит хосту");
        break;
    case QAbstractSocket::UnsupportedSocketOperationError:
        s = QStringLiteral("операция не поддерживается локальной операционной системой");
        break;
    default:
        s = QStringLiteral("неизвестная ошибка сокета");
        break;
    }
    return s;
}
 
static QString saslAuthConditionToString(QCA::SASL::AuthCondition x)
{
    QString s;
    switch (x) {
    case QCA::SASL::NoMechanism:
        s = QStringLiteral("не удалось согласовать подходящий механизм");
        break;
    case QCA::SASL::BadProtocol:
        s = QStringLiteral("недействительный протокол SASL");
        break;
    case QCA::SASL::BadServer:
        s = QStringLiteral("сбой взаимной аутентификации сервера");
        break;
    // AuthFail или неизвестная ошибка (включая те, которые определены только для сервера).
    default:
        s = QStringLiteral("общая ошибка аутентификации");
        break;
    };
    return s;
}
 
class ClientTest : public QObject
{
    Q_OBJECT
 
private:
    QString     host, proto, authzid, realm, user, pass;
    int         port;
    bool        no_authzid, no_realm;
    int mode; // 0 = получение списка механизмов, 1 = согласование SASL, 2 = приложение.
    QTcpSocket *sock;
    QCA::SASL * sasl;
    QByteArray  inbuf;
    bool        sock_done;
    int         waitCycles;
 
public:
    ClientTest(const QString &_host,
               int            _port,
               const QString &_proto,
               const QString &_authzid,
               const QString &_realm,
               const QString &_user,
               const QString &_pass,
               bool           _no_authzid,
               bool           _no_realm)
        : host(_host)
        , proto(_proto)
        , authzid(_authzid)
        , realm(_realm)
        , user(_user)
        , pass(_pass)
        , port(_port)
        , no_authzid(_no_authzid)
        , no_realm(_no_realm)
        , sock_done(false)
        , waitCycles(0)
    {
        sock = new QTcpSocket(this);
        connect(sock, &QTcpSocket::connected, this, &ClientTest::sock_connected);
        connect(sock, &QTcpSocket::readyRead, this, &ClientTest::sock_readyRead);
        connect(sock, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &ClientTest::sock_error);
 
        sasl = new QCA::SASL(this);
        connect(sasl, &QCA::SASL::clientStarted, this, &ClientTest::sasl_clientFirstStep);
        connect(sasl, &QCA::SASL::nextStep, this, &ClientTest::sasl_nextStep);
        connect(sasl, &QCA::SASL::needParams, this, &ClientTest::sasl_needParams);
        connect(sasl, &QCA::SASL::authenticated, this, &ClientTest::sasl_authenticated);
        connect(sasl, &QCA::SASL::readyRead, this, &ClientTest::sasl_readyRead);
        connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ClientTest::sasl_readyReadOutgoing);
        connect(sasl, &QCA::SASL::error, this, &ClientTest::sasl_error);
    }
 
public Q_SLOTS:
    void start()
    {
        mode = 0; // Режим списка механизмов.
 
        int flags = 0;
        flags |= QCA::SASL::AllowPlain;
        flags |= QCA::SASL::AllowAnonymous;
        sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256);
 
        if (!user.isEmpty())
            sasl->setUsername(user);
        if (!authzid.isEmpty())
            sasl->setAuthzid(authzid);
        if (!pass.isEmpty())
            sasl->setPassword(pass.toUtf8());
        if (!realm.isEmpty())
            sasl->setRealm(realm);
 
        printf("Подключение к %s:%d, для протокола %s\n", qPrintable(host), port, qPrintable(proto));
        sock->connectToHost(host, port);
    }
 
Q_SIGNALS:
    void quit();
 
private Q_SLOTS:
    void sock_connected()
    {
        printf("Подключено к серверу. Ожидание списка механизмов…\n");
    }
 
    void sock_error(QAbstractSocket::SocketError x)
    {
        if (x == QAbstractSocket::RemoteHostClosedError) {
            if (mode == 2) // Режим приложения, где отключение означает завершение.
            {
                sock_done = true;
                tryFinished();
                return 0;
        } else // Любой другой режим, где отключение — это ошибка.
            {
                printf("Ошибка: сервер неожиданно закрыл соединение.\n");
                emit quit();
                return 0;
            }
        }
 
        printf("Ошибка: сокет = %s\n", qPrintable(socketErrorToString(x)));
        emit quit();
    }
 
    void sock_readyRead()
    {
        if (mode == 2) // Режим приложения.
        {
            QByteArray a = sock->readAll();
            printf("Прочитать %d байт\n", a.size());
 
            // Возможный недостаток QCA API 2.0 состоит в том,
            // что если данные SASL получены от однорангового узла
            // с последующим отключением от однорангового узла,
            // нет чёткого подхода к восстановлению байтов, которые могут быть потеряны при прерывании/закрытии соединения. Для TLS
            // этой проблемы не возникает, потому что TLS имеет концепцию
            // закрытия сеанса. У SASL нет такой концепции работы с завершением сеанса,
            // и поскольку QCA API является асинхронным,
            // потенциально алгоритм мог бы вечно ожидать декодированные
            // данные, если бы последняя запись содержала бы только часть данных.
            //
            // На текущий момент можно применить простой обходной путь.
            // Нужно подождать по крайней мере три цикла обработки событий для
            // декодированных данных, прежде чем закончить работу, предположив,
            // что последняя запись была частичной. Дело в том, что все текущие
            // криптопровайдеры QCA SASL отвечают в течение такого периода времени,
            // так что подобный подход должен хорошо работать.  
            // В QCA 2.1 необходимо пересмотреть API, чтобы
            // лучше справляться с этой проблемой.
            //
            // Следующее примечание: предполагается, что такая особенность влияет только на
            // протоколы приложений, которые не отправляют собственное сообщение
            // о закрытии и полагаются на закрытие на уровне TCP. Примерами
            // являются HTTP и, конечно же, протокол QCATest.
            if (waitCycles == 0) {
                waitCycles = 3;
                QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
            }
 
            sasl->writeIncoming(a);
        } else // Список механизмов или режим согласования SASL.
        {
            if (sock->canReadLine()) {
                QString line = QString::fromLatin1(sock->readLine());
                line.truncate(line.length() - 1); // Обрезается новая строка.
                handleLine(line);
            }
        }
    }
 
    void sasl_clientFirstStep(bool clientInit, const QByteArray &clientInitData)
    {
        printf("Выбор механизма: %s\n", qPrintable(sasl->mechanism()));
        QString line = sasl->mechanism();
        if (clientInit) {
            line += QLatin1Char(' ');
            line += arrayToString(clientInitData);
        }
        sendLine(line);
    }
 
    void sasl_nextStep(const QByteArray &stepData)
    {
        QString line = QStringLiteral("C");
        if (!stepData.isEmpty()) {
            line += QLatin1Char(',');
            line += arrayToString(stepData);
        }
        sendLine(line);
    }
 
    void sasl_needParams(const QCA::SASL::Params &params)
    {
        if (params.needUsername()) {
            user = prompt(QStringLiteral("Имя пользователя:"));
            sasl->setUsername(user);
        }
 
        if (params.canSendAuthzid() && !no_authzid) {
            authzid = prompt(QStringLiteral("Авторизоваться как (нажмите enter, чтобы пропустить шаг):"));
            if (!authzid.isEmpty())
                sasl->setAuthzid(authzid);
        }
 
        if (params.needPassword()) {
            QCA::ConsolePrompt prompt;
            prompt.getHidden(QStringLiteral("* Пароль"));
            prompt.waitForFinished();
            QCA::SecureArray pass = prompt.result();
            sasl->setPassword(pass);
        }
 
        if (params.canSendRealm() && !no_realm) {
            QStringList realms = sasl->realmList();
            printf("Доступные области:\n");
            if (realms.isEmpty())
            printf(" (не указано)\n");
            foreach (const QString &s, realms)
                printf("  %s\n", qPrintable(s));
            realm = prompt(QStringLiteral("Область (нажмите enter, чтобы пропустить шаг):"));
            if (!realm.isEmpty())
                sasl->setRealm(realm);
        }
 
        sasl->continueAfterParams();
    }
 
    void sasl_authenticated()
    {
        printf("Успешная аутентификация SASL!\n");
        printf("SSF: %d\n", sasl->ssf());
    }
 
    void sasl_readyRead()
    {
        QByteArray a = sasl->read();
        inbuf += a;
        processInbuf();
    }
 
    void sasl_readyReadOutgoing()
    {
        QByteArray a = sasl->readOutgoing();
        sock->write(a);
    }
 
    void sasl_error()
    {
        int e = sasl->errorCode();
        if (e == QCA::SASL::ErrorInit)
            printf("Ошибка SASL: ошибка инициализации.\n");
        else if (e == QCA::SASL::ErrorHandshake)
            printf("Ошибка SASL: %s.\n", qPrintable(saslAuthConditionToString(sasl->authCondition())));
        else if (e == QCA::SASL::ErrorCrypt)
            printf("Ошибка SASL: нарушен уровень безопасности.\n");
        else
            printf("Ошибка SASL: неизвестная ошибка.\n");
 
        emit quit();
    }
 
    void waitWriteIncoming()
    {
        --waitCycles;
        if (waitCycles > 0) {
            QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
            return 0;
        }
 
        tryFinished();
    }
 
private:
    void tryFinished()
    {
        if (sock_done && waitCycles == 0) {
            printf("Завершено, соединение с сервером закрыто.\n");
 
            // Если не реализовывать ожидание ответа от
            // writeIncoming, то он может прийти слишком поздно.  
            // Теоретически это не должно случиться, если приложение ждёт в течение
            // достаточного количества циклов, но иногда ответ может
            // вернуться после запроса на выход из приложения,
            // но до фактического выхода из приложения. Чтобы упростить
            // отладку, приложение явно перестаёт здесь
            // обрабатывать сигналы. В противном случае ответ всё равно
            // может быть получен и отображён, что создаст ложное
            // ощущение правильности работы.
            sasl->disconnect(this);
 
            emit quit();
        }
    }
 
    QString arrayToString(const QByteArray &ba)
    {
        return QCA::Base64().arrayToString(ba);
    }
 
    QByteArray stringToArray(const QString &s)
    {
        return QCA::Base64().stringToArray(s).toByteArray();
    }
 
    void sendLine(const QString &line)
    {
        printf("Запись: {%s}\n", qPrintable(line));
        QString    s = line + QLatin1Char('\n');
        QByteArray a = s.toUtf8();
        if (mode == 2) // Режим приложения.
            sasl->write(a); // Запись в SASL.
        else // Список механизмов или согласование SASL.
            sock->write(a); // Запись в сокет.
    }
 
    void processInbuf()
    {
        // Готовые строки собираются из inbuf.
        QStringList list;
        int         at;
        while ((at = inbuf.indexOf('\n')) != -1) {
            list += QString::fromUtf8(inbuf.mid(0, at));
            inbuf = inbuf.mid(at + 1);
        }
 
        // Обработка строк.
        foreach (const QString &line, list)
            handleLine(line);
    }
 
    void handleLine(const QString &line)
    {
        printf("Чтение: [%s]\n", qPrintable(line));
        if (mode == 0) {
            // Первая строка — это список методов.
            const QStringList mechlist = line.split(QLatin1Char(' '));
            mode = 1; // Переключение в режим согласования SASL.
            sasl->startClient(proto, host, mechlist);
        } else if (mode == 1) {
            QString type, rest;
            int     n = line.indexOf(QLatin1Char(','));
            if (n != -1) {
                type = line.mid(0, n);
                rest = line.mid(n + 1);
            } else
                type = line;
 
            if (type == QLatin1String("C")) {
                sasl->putStep(stringToArray(rest));
            } else if (type == QLatin1String("E")) {
                if (!rest.isEmpty())
                    printf("Ошибка. Сервер сообщает: %s.\n", qPrintable(rest));
                else
                    printf("Неизвестная ошибка сервера.\n");
                emit quit();
                return 0;
            } else if (type == QLatin1String("A")) {
                printf("Успешная аутентификация.\n");
        mode = 2; // Переключение в режим приложения.
 
                // В этот момент сервер может отправить текстовые
                // строки для отображения, а затем закрыть соединение.
 
                sock_readyRead(); // Какие-нибудь дополнительные данные?
                return 0;
            } else {
                printf("Ошибка: недопустимый формат данных. Соединение закрывается.\n");
                emit quit();
                return 0;
            }
        }
    }
};
 
void usage()
{
printf("использование: saslclient (options) host(:port) (user) (pass)\n");
printf("опции: --proto=x, --authzid=x, --realm=x\n");
}
 
int main(int argc, char **argv)
{
    QCA::Initializer init;
    QCoreApplication qapp(argc, argv);
 
    QStringList args = qapp.arguments();
    args.removeFirst();
 
    // Опции.
    QString proto = QStringLiteral("qcatest"); // Протокол по умолчанию.
    QString authzid, realm;
    bool    no_authzid = false;
    bool    no_realm   = false;
    for (int n = 0; n < args.count(); ++n) {
        if (!args[n].startsWith(QLatin1String("--")))
            continue;
 
        QString opt = args[n].mid(2);
        QString var, val;
        int     at = opt.indexOf(QLatin1Char('='));
        if (at != -1) {
            var = opt.mid(0, at);
            val = opt.mid(at + 1);
        } else
            var = opt;
 
        if (var == QLatin1String("proto")) {
            proto = val;
        } else if (var == QLatin1String("authzid")) {
            // Указание пустого authzid означает, что данные намеренно не указаны.
            if (val.isEmpty())
                no_authzid = true;
            else
                authzid = val;
        } else if (var == QLatin1String("realm")) {
            // Указание пустого realm означает, что данные намеренно не указаны.
            if (val.isEmpty())
                no_realm = true;
            else
                realm = val;
        }
 
        args.removeAt(n);
        --n; // Смещение позиции.
    }
 
    if (args.count() < 1) {
        usage();
        return 0;
    }
 
    QString host, user, pass;
    int port = 8001; // Порт по умолчанию.
 
    QString hostinput = args[0];
    if (args.count() >= 2)
        user = args[1];
    if (args.count() >= 3)
        pass = args[2];
 
    int at = hostinput.indexOf(QLatin1Char(':'));
    if (at != -1) {
        host = hostinput.mid(0, at);
        port = hostinput.midRef(at + 1).toInt();
    } else
        host = hostinput;
 
    if (!QCA::isSupported("sasl")) {
        printf("Ошибка: поддержка SASL не найдена.\n");
        return 1;
    }
 
    ClientTest client(host, port, proto, authzid, realm, user, pass, no_authzid, no_realm);
    QObject::connect(&client, &ClientTest::quit, &qapp, &QCoreApplication::quit);
    QTimer::singleShot(0, &client, &ClientTest::start);
    qapp.exec();
 
    return 0;
}
 
#include "saslclient.moc"

Мы используем cookies для персонализации сайта и его более удобного использования. Вы можете запретить cookies в настройках браузера.

Пожалуйста ознакомьтесь с политикой использования cookies.