/*
 * PAM API Qt wrapper
 * SPDX-FileCopyrightText: 2013 Martin Bříza <mbriza@redhat.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 */

#ifndef PAMHANDLE_H
#define PAMHANDLE_H

#include <QtCore/QObject>
#include <QtCore/QProcessEnvironment>
#include <security/pam_appl.h>

namespace PLASMALOGIN
{
class PamBackend;
/**
 * Class wrapping the standard Linux-PAM library calls
 *
 * Almost everything is left the same except the following things:
 *
 * Mainly, state returns - if you call pam_start, pam_open_session and then
 * pam_start again, the session will get closed and the conversation closed
 *
 * You don't need to pass PAM_SILENT to every call if you want PAM to be quiet.
 * You can set the flag globally by using the \ref setSilence method.
 *
 * \ref acctMgmt doesn't require you to handle the PAM_NEW_AUTHTOK_REQD condition,
 * it calls chAuthTok on its own.
 *
 * Error messages are automatically reported to qDebug
 */
class PamHandle
{
public:
    /**
     * ctor
     * \param parent parent backend
     */
    explicit PamHandle(PamBackend *parent = 0);

    virtual ~PamHandle();

    /**
     * Returns whether the session is open.
     * \sa openSession
     */
    bool isOpen() const;

    /**
     * pam_set_item - set and update PAM informations
     *
     * \param item_type PAM item type
     * \param item item pointer
     *
     * \return true on success
     */
    bool setItem(int item_type, const void *item);

    /**
     * pam_get_item - getting PAM informations
     *
     * \param item_type
     *
     * \return item pointer or NULL on failure
     */
    const void *getItem(int item_type);

    /**
     * pam_open_session - start PAM session management
     *
     * \return true on success
     */
    bool openSession();

    /**
     * pam_close_session - terminate PAM session management
     *
     * \return true on success
     */
    bool closeSession();

    /**
     * pam_setcred - establish / delete user credentials
     *
     * \param flags PAM flag(s)
     *
     * \return true on success
     */
    bool setCred(int flags = 0);

    /**
     * pam_authenticate - account authentication
     *
     * \param flags PAM flag(s)
     *
     * \return true on success
     */
    bool authenticate(int flags = 0);

    /**
     * pam_acct_mgmt - PAM account validation management
     *
     * @note Automatically calls \ref chAuthTok if the password is expired
     *
     * \param flags PAM flag(s)
     *
     * \return true on success
     */
    bool acctMgmt(int flags = 0);

    /**
     * pam_chauthtok - updating authentication tokens
     *
     * \param flags PAM flag(s)
     *
     * \return true on success
     */
    bool chAuthTok(int flags = 0);

    /**
     * pam_getenv - get PAM environment
     *
     * \return Complete process environment
     */
    QProcessEnvironment getEnv();

    /**
     * pam_putenv - set or change PAM environment
     *
     * \param env environment to be merged into the PAM one
     *
     * \return true on success
     */
    bool putEnv(const QProcessEnvironment &env);

    /**
     * pam_end - termination of PAM transaction
     *
     * \param flags to be OR'd with the status (PAM_DATA_SILENT)
     * \return true on success
     */
    bool end(int flags = 0);

    /**
     * pam_start - initialization of PAM transaction
     *
     * \param service PAM service name, e.g. "plasmalogin"
     * \param pam_conversation pointer to the PAM conversation structure to be used
     * \param user username
     *
     * \return true on success
     */
    bool start(const QString &service, const QString &user = QString());

    /**
     * Set PAM_SILENT upon the contained calls
     * \param silent true if silent
     */
    void setSilence(bool silent);

    /**
     * Generates an error message according to the internal state
     *
     * \return error string
     */
    QString errorString();

private:
    /**
     * Conversation function for the pam_conv structure
     *
     * Calls ((PamHandle*)pam_conv.appdata_ptr)->doConverse() with its parameters
     *
     * Not to be called directly, therefore private
     */
    static int converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data);

    int m_silent{0}; ///< flag mask for silence of the contained calls

    struct pam_conv m_conv; ///< the current conversation
    pam_handle_t *m_handle{nullptr}; ///< the actual PAM handle
    int m_result{0}; ///< PAM result
    bool m_open{false}; ///< whether the session is open
};
}

#endif // PAMHANDLE_H
