/*
 * Copyright (C) 2018 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser 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, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "viralloc.h"
#include "virfile.h"
#include "virstring.h"
#include "virerror.h"

#include "security_util.h"

#define VIR_FROM_THIS VIR_FROM_SECURITY

/* There are four namespaces available on Linux (xattr(7)):
 *
 *  user - can be modified by anybody,
 *  system - used by ACLs
 *  security - used by SELinux
 *  trusted - accessibly by CAP_SYS_ADMIN processes only
 *
 * Looks like the last one is way to go.
 * Unfortunately, FreeBSD only supports:
 *
 *  user - can be modified by anybody,
 *  system - accessible by CAP_SYS_ADMIN processes only
 *
 * Note that 'system' on FreeBSD corresponds to 'trusted' on
 * Linux. So far the only point where FreeBSD and Linux can meet
 * is NFS which still doesn't support XATTRs. Therefore we can
 * use different namespace on each system. If NFS gains support
 * for XATTRs then we have to find a way to deal with the
 * different namespaces. But that is a problem for future me.
 */
#if defined(__linux__)
# define XATTR_NAMESPACE "trusted"
#elif defined(__FreeBSD__)
# define XATTR_NAMESPACE "system"
#endif

static char *
virSecurityGetAttrName(const char *name ATTRIBUTE_UNUSED)
{
    char *ret = NULL;
#ifdef XATTR_NAMESPACE
    ignore_value(virAsprintf(&ret, XATTR_NAMESPACE".libvirt.security.%s", name));
#else
    errno = ENOSYS;
    virReportSystemError(errno, "%s",
                         _("Extended attributes are not supported on this system"));
#endif
    return ret;
}


static char *
virSecurityGetRefCountAttrName(const char *name ATTRIBUTE_UNUSED)
{
    char *ret = NULL;
#ifdef XATTR_NAMESPACE
    ignore_value(virAsprintf(&ret, XATTR_NAMESPACE".libvirt.security.ref_%s", name));
#else
    errno = ENOSYS;
    virReportSystemError(errno, "%s",
                         _("Extended attributes are not supported on this system"));
#endif
    return ret;
}


/**
 * virSecurityGetRememberedLabel:
 * @name: security driver name
 * @path: file name
 * @label: label
 *
 * For given @path and security driver (@name) fetch remembered
 * @label. The caller must not restore label if an error is
 * indicated or if @label is NULL upon return.
 *
 * The idea is that the first time
 * virSecuritySetRememberedLabel() is called over @path the
 * @label is recorded and refcounter is set to 1. Each subsequent
 * call to virSecuritySetRememberedLabel() increases the counter.
 * Counterpart to this is virSecurityGetRememberedLabel() which
 * decreases the counter and reads the @label only if the counter
 * reached value of zero. For any other call (i.e. when the
 * counter is not zero), virSecurityGetRememberedLabel() sets
 * @label to NULL (to notify the caller that the refcount is not
 * zero) and returns zero.
 *
 * Returns: 0 on success,
 *         -1 otherwise (with error reported)
 */
int
virSecurityGetRememberedLabel(const char *name,
                              const char *path,
                              char **label)
{
    char *ref_name = NULL;
    char *attr_name = NULL;
    char *value = NULL;
    unsigned int refcount = 0;
    int ret = -1;

    *label = NULL;

    if (!(ref_name = virSecurityGetRefCountAttrName(name)))
        goto cleanup;

    if (virFileGetXAttr(path, ref_name, &value) < 0) {
        if (errno == ENOSYS || errno == ENODATA || errno == ENOTSUP) {
            ret = 0;
        } else {
            virReportSystemError(errno,
                                 _("Unable to get XATTR %s on %s"),
                                 ref_name,
                                 path);
        }
        goto cleanup;
    }

    if (virStrToLong_ui(value, NULL, 10, &refcount) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("malformed refcount %s on %s"),
                       value, path);
        goto cleanup;
    }

    VIR_FREE(value);

    refcount--;

    if (refcount > 0) {
        if (virAsprintf(&value, "%u", refcount) < 0)
            goto cleanup;

        if (virFileSetXAttr(path, ref_name, value) < 0)
            goto cleanup;
    } else {
        if (virFileRemoveXAttr(path, ref_name) < 0)
            goto cleanup;

        if (!(attr_name = virSecurityGetAttrName(name)))
            goto cleanup;

        if (virFileGetXAttr(path, attr_name, label) < 0)
            goto cleanup;

        if (virFileRemoveXAttr(path, attr_name) < 0)
            goto cleanup;
    }

    ret = 0;
 cleanup:
    VIR_FREE(value);
    VIR_FREE(attr_name);
    VIR_FREE(ref_name);
    return ret;
}


/**
 * virSecuritySetRememberedLabel:
 * @name: security driver name
 * @path: file name
 * @label: label
 *
 * For given @path and security driver (@name), if called the
 * first time over @path, set the @label to remember (i.e. the
 * original owner of the @path). Any subsequent call over @path
 * will increment refcounter. It is strongly recommended that the
 * caller checks for the return value and if it is greater than 1
 * (meaning that some domain is already using @path) the current
 * label is required instead of setting a new one.
 *
 * See also virSecurityGetRememberedLabel.
 *
 * Returns: the new refcount value on success,
 *         -1 otherwise (with error reported)
 */
int
virSecuritySetRememberedLabel(const char *name,
                              const char *path,
                              const char *label)
{
    char *ref_name = NULL;
    char *attr_name = NULL;
    char *value = NULL;
    unsigned int refcount = 0;
    int ret = -1;

    if (!(ref_name = virSecurityGetRefCountAttrName(name)))
        goto cleanup;

    if (virFileGetXAttr(path, ref_name, &value) < 0) {
        if (errno == ENOSYS || errno == ENOTSUP) {
            ret = 0;
            goto cleanup;
        } else if (errno != ENODATA) {
            virReportSystemError(errno,
                                 _("Unable to get XATTR %s on %s"),
                                 ref_name,
                                 path);
            goto cleanup;
        }
    }

    if (value &&
        virStrToLong_ui(value, NULL, 10, &refcount) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("malformed refcount %s on %s"),
                       value, path);
        goto cleanup;
    }

    VIR_FREE(value);

    refcount++;

    if (refcount == 1) {
        if (!(attr_name = virSecurityGetAttrName(name)))
            goto cleanup;

        if (virFileSetXAttr(path, attr_name, label) < 0)
            goto cleanup;
    }

    if (virAsprintf(&value, "%u", refcount) < 0)
        goto cleanup;

    if (virFileSetXAttr(path, ref_name, value) < 0)
        goto cleanup;

    ret = refcount;
 cleanup:
    VIR_FREE(value);
    VIR_FREE(attr_name);
    VIR_FREE(ref_name);
    return ret;
}
