/*
 * Copyright 2013 Canonical Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 3 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <errno.h>
#include <lomiri/util/Dbus.h>
#include <lomiri/util/SnapPath.h>
#include <assert.h>
#include <sys/types.h>
#include <unistd.h>
#include <QDBusConnection>
#include <QDir>
#include <QStandardPaths>
#include <lomiri/transfers/system/logger.h>
#include <sys/apparmor.h>
#include "apparmor.h"
#include "dbus_proxy_factory.h"
#include "pending_reply.h"
#include "uuid_utils.h"

namespace Lomiri {

namespace Transfers {

namespace System {

QString AppArmor::UNCONFINED_ID = "unconfined";
QString AppArmor::LINUX_SECURITY_LABEL = "LinuxSecurityLabel";


AppArmor::AppArmor(DBusConnection* connection,
                QObject* parent)
    : QObject(parent) {
    _dbus = DBusProxyFactory::instance()->createDBusProxy(connection, this);
    _uuidFactory = new UuidFactory(this);
}

AppArmor::~AppArmor() {
    delete _dbus;
    delete _uuidFactory;
}

SecurityDetails*
AppArmor::getSecurityDetails(const QString& connName) {
    auto id = UuidUtils::getDBusString(_uuidFactory->createUuid());
    auto details = new SecurityDetails(id);
    getSecurityDetails(connName, details);
    return details;
}

SecurityDetails*
AppArmor::getSecurityDetails(const QString& connName,
                             const QString& id) {

    auto details = new SecurityDetails(id);
    getSecurityDetails(connName, details);
    return details;
}

QString
AppArmor::labelFromCredentials(const QVariantMap &map)
{
    // The contents of this map are described in the specification here:
    // http://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-get-connection-credentials
    QByteArray label = map.value(LINUX_SECURITY_LABEL).value<QByteArray>();

    if (label.size() == 0) {
        return "";
    }

    // The label is null terminated.
    assert(label[label.size()-1] == '\0');
    label.truncate(label.size() - 1);
    // Trim the mode off the end of the label.
    int pos = label.lastIndexOf(' ');
    if (pos > 0 && label.endsWith(')') && label[pos+1] == '(')
    {
        label.truncate(pos);  // LCOV_EXCL_LINE
    }
    return QString::fromUtf8(label.constData(), label.size());
}

bool
AppArmor::isAaEnabled(void) {
    char* envvar = getenv("LDM_MOCK_AA_POSITIVE");
    if (envvar != NULL && strlen (envvar) == 1) {
        switch (envvar[0]) {
            case '0':
                return false;
                break;
            case '1':
                return true;
                break;
        }
    }

    return aa_is_enabled();
}

QString
AppArmor::appId(QString caller) {
    if (!isAaEnabled()) {
        return "";
    }

    QScopedPointer<PendingReply<QVariantMap> > reply(
        _dbus->GetConnectionCredentials(caller));
    // blocking but should be ok for now
    reply->waitForFinished();
    if (reply->isError()) {
        return "";
    }
    return labelFromCredentials(reply->value());
}

bool
AppArmor::isConfined(QString appId) {
    if (appId.isEmpty() || appId == UNCONFINED_ID) {
        return false;
    }
    return true;
}

void
AppArmor::getSecurityDetails(const QString& connName,
                             SecurityDetails* details) {
    if (!isAaEnabled() || connName.isEmpty()) {
        details->dbusPath = QString(BASE_ACCOUNT_URL) + "/" + details->id;
        details->localPath = getLocalPath("");
        details->isConfined = false;
        return;
    }

    QScopedPointer<PendingReply<QVariantMap> > reply(
        _dbus->GetConnectionCredentials(connName));
    // blocking but should be ok for now
    reply->waitForFinished();
    if (reply->isError()) {
        LOG(ERROR) << reply->error();
        details->dbusPath = QString(BASE_ACCOUNT_URL) + "/" + details->id;
        details->localPath = getLocalPath("");
        details->isConfined = false;
        return;
    } else {
        // use the returned value
        details->appId = labelFromCredentials(reply->value());

        if (details->appId.isEmpty() || details->appId == UNCONFINED_ID) {
            LOG(INFO) << "UNCONFINED APP";
            details->dbusPath = QString(BASE_ACCOUNT_URL) + "/" + details->id;
            details->localPath = getLocalPath("");
            details->isConfined = false;
            return;
        } else {
            details->isConfined = true;

            auto stdPath = lomiri::util::dbus_sanitized_path(BASE_ACCOUNT_URL, details->appId.toStdString());
            QString path = QString::fromStdString(stdPath);

            LOG(INFO) << "AppId path is " << path;

            details->dbusPath = path + "/" + details->id;
            details->localPath = getLocalPath(details->appId);
            return;
        }  // not empty appid string
    }  // no dbus error
}

QPair<QString, QString>
AppArmor::getDBusPath() {
    QUuid uuid = _uuidFactory->createUuid();
    QString id = UuidUtils::getDBusString(uuid);
    QString dbusPath = UuidUtils::getDBusString(uuid);
    return QPair<QString, QString>(id, dbusPath);
}

QString
AppArmor::getLocalPath(const QString& appId) {
    // if the service is running as root we will always return /tmp
    // as the local path root
    if (getuid() == 0){
        LOG(INFO) << "Running as system bus using /tmp for downloads";
        return QStandardPaths::writableLocation(
            QStandardPaths::TempLocation);
    } else {
        QString dataPath = QStandardPaths::writableLocation(
            QStandardPaths::AppLocalDataLocation);
        QStringList pathComponents;

        if (!appId.isEmpty()) {
            QStringList appIdInfo = appId.split("_");
            if (appIdInfo.count() > 0) {
                if (appIdInfo[0].startsWith("snap.")) {
                    // Snap path
                    QStringList appComponents = appIdInfo[0].split(".");
                    if (QDir::homePath().contains(QStringLiteral("snap/lomiri-download-manager"))) {
                        // We're running inside a snap so adjust the $HOME location accordingly
                        pathComponents << QDir::homePath() << ".." << ".." << appComponents[1] << "common";
                    } else {
                        pathComponents << QDir::homePath() << "snap" << appComponents[1] << "common";
                    }
                } else {
                    // Click path
                    pathComponents << dataPath << appIdInfo[0];
                }
            }
        } else {
            pathComponents << dataPath;
        }

        pathComponents << "Downloads";

        QString path = pathComponents.join(QDir::separator());

        bool wasCreated = QDir().mkpath(path);
        if (!wasCreated) {
            LOG(ERROR) << "Could not create the data path"
                << path;
        }
        LOG(INFO) << "Local path is" << path;
        return path;
    }  // not root
}

}  // System

}  // Transfers

}  // Lomiri
