2012-01-20 17:49:32 +00:00
|
|
|
/*
|
|
|
|
* viridentity.c: helper APIs for managing user identities
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012-2013 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>
|
|
|
|
|
2013-03-06 11:00:16 +00:00
|
|
|
#include <unistd.h>
|
2021-04-29 14:40:33 +00:00
|
|
|
#include <fcntl.h>
|
2013-03-20 13:06:04 +00:00
|
|
|
#if WITH_SELINUX
|
2013-03-06 11:00:16 +00:00
|
|
|
# include <selinux/selinux.h>
|
|
|
|
#endif
|
|
|
|
|
2021-04-29 14:40:33 +00:00
|
|
|
#define LIBVIRT_VIRIDENTITYPRIV_H_ALLOW
|
|
|
|
|
2012-01-20 17:49:32 +00:00
|
|
|
#include "internal.h"
|
|
|
|
#include "viralloc.h"
|
|
|
|
#include "virerror.h"
|
2021-04-29 14:40:33 +00:00
|
|
|
#include "viridentitypriv.h"
|
2012-01-20 17:49:32 +00:00
|
|
|
#include "virlog.h"
|
|
|
|
#include "virobject.h"
|
2021-04-29 14:40:33 +00:00
|
|
|
#include "virrandom.h"
|
2012-01-20 17:49:32 +00:00
|
|
|
#include "virthread.h"
|
2013-03-06 11:00:16 +00:00
|
|
|
#include "virutil.h"
|
2013-05-24 07:19:51 +00:00
|
|
|
#include "virstring.h"
|
2013-08-28 14:22:05 +00:00
|
|
|
#include "virprocess.h"
|
2019-07-26 15:36:29 +00:00
|
|
|
#include "virtypedparam.h"
|
2021-04-29 14:40:33 +00:00
|
|
|
#include "virfile.h"
|
|
|
|
#include "configmake.h"
|
2012-01-20 17:49:32 +00:00
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_IDENTITY
|
|
|
|
|
2020-11-19 12:26:17 +00:00
|
|
|
#define VIR_CONNECT_IDENTITY_SYSTEM_TOKEN "system.token"
|
|
|
|
|
2014-02-28 12:16:17 +00:00
|
|
|
VIR_LOG_INIT("util.identity");
|
2012-01-20 17:49:32 +00:00
|
|
|
|
|
|
|
struct _virIdentity {
|
2019-09-19 14:38:03 +00:00
|
|
|
GObject parent;
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2019-07-26 15:27:25 +00:00
|
|
|
int nparams;
|
|
|
|
int maxparams;
|
|
|
|
virTypedParameterPtr params;
|
2012-01-20 17:49:32 +00:00
|
|
|
};
|
|
|
|
|
2019-09-19 14:38:03 +00:00
|
|
|
G_DEFINE_TYPE(virIdentity, vir_identity, G_TYPE_OBJECT)
|
|
|
|
|
2013-03-06 10:53:47 +00:00
|
|
|
static virThreadLocal virIdentityCurrent;
|
2021-04-29 14:40:33 +00:00
|
|
|
static char *systemToken;
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2019-09-19 14:38:03 +00:00
|
|
|
static void virIdentityFinalize(GObject *obj);
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2019-09-19 14:38:03 +00:00
|
|
|
static void virIdentityCurrentCleanup(void *ident)
|
2012-01-20 17:49:32 +00:00
|
|
|
{
|
2019-09-19 14:38:03 +00:00
|
|
|
if (ident)
|
|
|
|
g_object_unref(ident);
|
|
|
|
}
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2019-09-19 14:38:03 +00:00
|
|
|
static int virIdentityOnceInit(void)
|
|
|
|
{
|
2013-03-06 10:53:47 +00:00
|
|
|
if (virThreadLocalInit(&virIdentityCurrent,
|
2019-09-19 14:38:03 +00:00
|
|
|
virIdentityCurrentCleanup) < 0) {
|
2013-03-06 10:53:47 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Cannot initialize thread local for current identity"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-04-29 14:40:33 +00:00
|
|
|
if (!(systemToken = virIdentityEnsureSystemToken()))
|
|
|
|
return -1;
|
|
|
|
|
2012-01-20 17:49:32 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-01-20 17:23:29 +00:00
|
|
|
VIR_ONCE_GLOBAL_INIT(virIdentity);
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2019-09-19 14:38:03 +00:00
|
|
|
static void vir_identity_init(virIdentity *ident G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vir_identity_class_init(virIdentityClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *obj = G_OBJECT_CLASS(klass);
|
|
|
|
|
|
|
|
obj->finalize = virIdentityFinalize;
|
|
|
|
}
|
|
|
|
|
2013-03-06 10:53:47 +00:00
|
|
|
/**
|
|
|
|
* virIdentityGetCurrent:
|
|
|
|
*
|
|
|
|
* Get the current identity associated with this thread. The
|
|
|
|
* caller will own a reference to the returned identity, but
|
|
|
|
* must not modify the object in any way, other than to
|
2019-09-19 14:38:03 +00:00
|
|
|
* release the reference when done with g_object_unref
|
2013-03-06 10:53:47 +00:00
|
|
|
*
|
|
|
|
* Returns: a reference to the current identity, or NULL
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
virIdentity *virIdentityGetCurrent(void)
|
2013-03-06 10:53:47 +00:00
|
|
|
{
|
2021-03-11 07:16:13 +00:00
|
|
|
virIdentity *ident;
|
2013-03-06 10:53:47 +00:00
|
|
|
|
2013-03-21 10:58:15 +00:00
|
|
|
if (virIdentityInitialize() < 0)
|
2013-03-06 10:53:47 +00:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ident = virThreadLocalGet(&virIdentityCurrent);
|
2019-09-19 14:38:03 +00:00
|
|
|
if (ident)
|
|
|
|
g_object_ref(ident);
|
|
|
|
return ident;
|
2013-03-06 10:53:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virIdentitySetCurrent:
|
|
|
|
*
|
|
|
|
* Set the new identity to be associated with this thread.
|
|
|
|
* The caller should not modify the passed identity after
|
|
|
|
* it has been set, other than to release its own reference.
|
|
|
|
*
|
|
|
|
* Returns 0 on success, or -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetCurrent(virIdentity *ident)
|
2013-03-06 10:53:47 +00:00
|
|
|
{
|
2021-09-03 14:04:44 +00:00
|
|
|
virIdentity *old = NULL;
|
2013-03-06 10:53:47 +00:00
|
|
|
|
2013-03-21 10:58:15 +00:00
|
|
|
if (virIdentityInitialize() < 0)
|
2013-03-06 10:53:47 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
old = virThreadLocalGet(&virIdentityCurrent);
|
|
|
|
|
|
|
|
if (virThreadLocalSet(&virIdentityCurrent,
|
2019-09-19 14:38:03 +00:00
|
|
|
ident ? g_object_ref(ident) : NULL) < 0) {
|
2013-03-06 10:53:47 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Unable to set thread local identity"));
|
2019-09-19 14:38:03 +00:00
|
|
|
if (ident)
|
|
|
|
g_object_unref(ident);
|
2013-03-06 10:53:47 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-09-03 14:04:44 +00:00
|
|
|
if (old)
|
|
|
|
g_object_unref(old);
|
2013-03-06 10:53:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2021-04-30 15:52:30 +00:00
|
|
|
/**
|
|
|
|
* virIdentityElevateCurrent:
|
|
|
|
*
|
|
|
|
* Set the new identity to be associated with this thread,
|
|
|
|
* to an elevated copy of the current identity. The old
|
|
|
|
* current identity is returned and should be released by
|
|
|
|
* the caller when no longer required.
|
|
|
|
*
|
|
|
|
* Returns the previous identity, or NULL on error
|
|
|
|
*/
|
|
|
|
virIdentity *virIdentityElevateCurrent(void)
|
|
|
|
{
|
|
|
|
g_autoptr(virIdentity) ident = virIdentityGetCurrent();
|
|
|
|
const char *token;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!ident) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("No current identity to elevate"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((rc = virIdentityGetSystemToken(ident, &token)) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (rc == 0) {
|
|
|
|
g_autoptr(virIdentity) identel = virIdentityNewCopy(ident);
|
|
|
|
|
|
|
|
if (virIdentitySetSystemToken(identel, systemToken) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (virIdentitySetCurrent(identel) < 0)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_steal_pointer(&ident);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void virIdentityRestoreHelper(virIdentity **identptr)
|
|
|
|
{
|
|
|
|
virIdentity *ident = *identptr;
|
|
|
|
|
2021-05-17 16:01:11 +00:00
|
|
|
if (ident != NULL) {
|
2021-04-30 15:52:30 +00:00
|
|
|
virIdentitySetCurrent(ident);
|
2021-05-17 16:01:11 +00:00
|
|
|
/* virIdentitySetCurrent() grabs its own reference.
|
|
|
|
* We don't need ours anymore. */
|
|
|
|
g_object_unref(ident);
|
|
|
|
}
|
2021-04-30 15:52:30 +00:00
|
|
|
}
|
|
|
|
|
2021-04-29 14:40:33 +00:00
|
|
|
#define TOKEN_BYTES 16
|
|
|
|
#define TOKEN_STRLEN (TOKEN_BYTES * 2)
|
|
|
|
|
|
|
|
static char *
|
|
|
|
virIdentityConstructSystemTokenPath(void)
|
|
|
|
{
|
|
|
|
g_autofree char *commondir = NULL;
|
|
|
|
if (geteuid() == 0) {
|
|
|
|
commondir = g_strdup(RUNSTATEDIR "/libvirt/common");
|
|
|
|
} else {
|
|
|
|
g_autofree char *rundir = virGetUserRuntimeDirectory();
|
|
|
|
commondir = g_strdup_printf("%s/common", rundir);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_mkdir_with_parents(commondir, 0700) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Cannot create daemon common directory '%s'"),
|
|
|
|
commondir);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_strdup_printf("%s/system.token", commondir);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
virIdentityEnsureSystemToken(void)
|
|
|
|
{
|
|
|
|
g_autofree char *tokenfile = virIdentityConstructSystemTokenPath();
|
|
|
|
g_autofree char *token = NULL;
|
|
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
if (!tokenfile)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
fd = open(tokenfile, O_RDWR|O_APPEND|O_CREAT, 0600);
|
|
|
|
if (fd < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Unable to open system token %s"),
|
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virSetCloseExec(fd) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to set close-on-exec flag '%s'"),
|
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virFileLock(fd, false, 0, 1, true) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to lock system token '%s'"),
|
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fstat(fd, &st) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to check system token '%s'"),
|
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ok, we're the first one here, so we must populate it */
|
|
|
|
if (st.st_size == 0) {
|
|
|
|
if (!(token = virRandomToken(TOKEN_BYTES))) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (safewrite(fd, token, TOKEN_STRLEN) != TOKEN_STRLEN) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to write system token '%s'"),
|
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (virFileReadLimFD(fd, TOKEN_STRLEN, &token) < 0) {
|
|
|
|
virReportSystemError(errno,
|
2021-07-20 15:02:31 +00:00
|
|
|
_("Failed to read system token '%s'"),
|
2021-04-29 14:40:33 +00:00
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (strlen(token) != TOKEN_STRLEN) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("System token in %s was corrupt"),
|
|
|
|
tokenfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_steal_pointer(&token);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-03-06 11:00:16 +00:00
|
|
|
/**
|
|
|
|
* virIdentityGetSystem:
|
|
|
|
*
|
|
|
|
* Returns an identity that represents the system itself.
|
|
|
|
* This is the identity that the process is running as
|
|
|
|
*
|
|
|
|
* Returns a reference to the system identity, or NULL
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
virIdentity *virIdentityGetSystem(void)
|
2013-03-06 11:00:16 +00:00
|
|
|
{
|
2019-10-01 16:38:12 +00:00
|
|
|
g_autofree char *username = NULL;
|
|
|
|
g_autofree char *groupname = NULL;
|
2013-08-22 15:58:58 +00:00
|
|
|
unsigned long long startTime;
|
2021-08-05 18:03:19 +00:00
|
|
|
g_autoptr(virIdentity) ret = virIdentityNew();
|
2013-03-20 13:06:04 +00:00
|
|
|
#if WITH_SELINUX
|
2020-07-15 10:32:48 +00:00
|
|
|
char *con;
|
2013-03-06 11:00:16 +00:00
|
|
|
#endif
|
2021-04-30 15:21:59 +00:00
|
|
|
g_autofree char *token = NULL;
|
2013-06-24 13:47:31 +00:00
|
|
|
|
2019-07-26 10:59:15 +00:00
|
|
|
if (virIdentitySetProcessID(ret, getpid()) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-08-28 14:22:05 +00:00
|
|
|
|
2013-08-22 15:58:58 +00:00
|
|
|
if (virProcessGetStartTime(getpid(), &startTime) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-08-22 15:58:58 +00:00
|
|
|
if (startTime != 0 &&
|
2019-07-26 10:59:15 +00:00
|
|
|
virIdentitySetProcessTime(ret, startTime) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-08-28 14:22:05 +00:00
|
|
|
|
2013-10-09 11:13:45 +00:00
|
|
|
if (!(username = virGetUserName(geteuid())))
|
2018-07-13 17:55:09 +00:00
|
|
|
return ret;
|
2019-07-26 10:59:15 +00:00
|
|
|
if (virIdentitySetUserName(ret, username) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-08-22 15:58:58 +00:00
|
|
|
if (virIdentitySetUNIXUserID(ret, getuid()) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-08-22 15:00:01 +00:00
|
|
|
|
2013-10-09 11:13:45 +00:00
|
|
|
if (!(groupname = virGetGroupName(getegid())))
|
2018-07-13 17:55:09 +00:00
|
|
|
return ret;
|
2019-07-26 10:59:15 +00:00
|
|
|
if (virIdentitySetGroupName(ret, groupname) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-08-22 15:58:58 +00:00
|
|
|
if (virIdentitySetUNIXGroupID(ret, getgid()) < 0)
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2013-03-06 11:00:16 +00:00
|
|
|
|
2013-03-20 13:06:04 +00:00
|
|
|
#if WITH_SELINUX
|
2014-03-20 15:05:14 +00:00
|
|
|
if (is_selinux_enabled() > 0) {
|
2014-03-06 06:02:48 +00:00
|
|
|
if (getcon(&con) < 0) {
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to lookup SELinux process context"));
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2014-03-06 06:02:48 +00:00
|
|
|
}
|
2013-08-22 15:58:58 +00:00
|
|
|
if (virIdentitySetSELinuxContext(ret, con) < 0) {
|
2014-03-06 06:02:48 +00:00
|
|
|
freecon(con);
|
2019-10-01 16:38:12 +00:00
|
|
|
return NULL;
|
2014-03-06 06:02:48 +00:00
|
|
|
}
|
2013-05-24 07:19:51 +00:00
|
|
|
freecon(con);
|
2013-03-06 11:00:16 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-04-30 15:21:59 +00:00
|
|
|
if (!(token = virIdentityEnsureSystemToken()))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (virIdentitySetSystemToken(ret, token) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
2019-10-01 16:38:12 +00:00
|
|
|
return g_steal_pointer(&ret);
|
2013-03-06 11:00:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-29 14:52:20 +00:00
|
|
|
/**
|
|
|
|
* virIdentityIsCurrentElevated:
|
|
|
|
*
|
|
|
|
* Determine if the current identity has elevated privileges.
|
|
|
|
* This indicates that it was invoked on behalf of the
|
|
|
|
* user by a libvirt daemon.
|
|
|
|
*
|
|
|
|
* Returns: true if elevated
|
|
|
|
*/
|
|
|
|
int virIdentityIsCurrentElevated(void)
|
|
|
|
{
|
|
|
|
g_autoptr(virIdentity) current = virIdentityGetCurrent();
|
|
|
|
const char *currentToken = NULL;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
if (!current) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("No current identity"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = virIdentityGetSystemToken(current, ¤tToken);
|
|
|
|
if (rv <= 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
return STREQ_NULLABLE(currentToken, systemToken);
|
|
|
|
}
|
|
|
|
|
2012-01-20 17:49:32 +00:00
|
|
|
/**
|
|
|
|
* virIdentityNew:
|
|
|
|
*
|
|
|
|
* Creates a new empty identity object. After creating, one or
|
|
|
|
* more identifying attributes should be set on the identity.
|
|
|
|
*
|
|
|
|
* Returns: a new empty identity
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
virIdentity *virIdentityNew(void)
|
2012-01-20 17:49:32 +00:00
|
|
|
{
|
2019-09-19 14:38:03 +00:00
|
|
|
return VIR_IDENTITY(g_object_new(VIR_TYPE_IDENTITY, NULL));
|
2012-01-20 17:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-30 15:46:15 +00:00
|
|
|
/**
|
|
|
|
* virIdentityNewCopy:
|
|
|
|
*
|
|
|
|
* Creates a new identity object that is a deep copy of an
|
|
|
|
* existing identity.
|
|
|
|
*
|
|
|
|
* Returns: a copy of the source identity
|
|
|
|
*/
|
|
|
|
virIdentity *virIdentityNewCopy(virIdentity *src)
|
|
|
|
{
|
|
|
|
g_autoptr(virIdentity) ident = virIdentityNew();
|
|
|
|
|
|
|
|
if (virTypedParamsCopy(&ident->params, src->params, src->nparams) < 0)
|
|
|
|
return NULL;
|
|
|
|
ident->nparams = src->nparams;
|
|
|
|
ident->maxparams = src->nparams;
|
|
|
|
|
|
|
|
return g_steal_pointer(&ident);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-19 14:38:03 +00:00
|
|
|
static void virIdentityFinalize(GObject *object)
|
2012-01-20 17:49:32 +00:00
|
|
|
{
|
2021-03-11 07:16:13 +00:00
|
|
|
virIdentity *ident = VIR_IDENTITY(object);
|
2012-01-20 17:49:32 +00:00
|
|
|
|
2019-07-26 15:27:25 +00:00
|
|
|
virTypedParamsFree(ident->params, ident->nparams);
|
2019-09-19 14:38:03 +00:00
|
|
|
|
|
|
|
G_OBJECT_CLASS(vir_identity_parent_class)->finalize(object);
|
2012-01-20 17:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetUserName(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
const char **username)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
*username = NULL;
|
|
|
|
return virTypedParamsGetString(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_USER_NAME,
|
|
|
|
username);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetUNIXUserID(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
uid_t *uid)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
unsigned long long val;
|
|
|
|
int rc;
|
2013-08-22 15:43:35 +00:00
|
|
|
|
|
|
|
*uid = -1;
|
2019-07-26 15:27:25 +00:00
|
|
|
rc = virTypedParamsGetULLong(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_USER_ID,
|
|
|
|
&val);
|
|
|
|
if (rc <= 0)
|
|
|
|
return rc;
|
2013-08-22 15:43:35 +00:00
|
|
|
|
|
|
|
*uid = (uid_t)val;
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
return 1;
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetGroupName(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
const char **groupname)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
*groupname = NULL;
|
|
|
|
return virTypedParamsGetString(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_GROUP_NAME,
|
|
|
|
groupname);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetUNIXGroupID(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
gid_t *gid)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
unsigned long long val;
|
|
|
|
int rc;
|
2013-08-22 15:43:35 +00:00
|
|
|
|
|
|
|
*gid = -1;
|
2019-07-26 15:27:25 +00:00
|
|
|
rc = virTypedParamsGetULLong(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_GROUP_ID,
|
|
|
|
&val);
|
|
|
|
if (rc <= 0)
|
|
|
|
return rc;
|
2013-08-22 15:43:35 +00:00
|
|
|
|
|
|
|
*gid = (gid_t)val;
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
return 1;
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetProcessID(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
pid_t *pid)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
long long val;
|
|
|
|
int rc;
|
2013-08-22 15:43:35 +00:00
|
|
|
|
|
|
|
*pid = 0;
|
2019-07-26 15:27:25 +00:00
|
|
|
rc = virTypedParamsGetLLong(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_ID,
|
|
|
|
&val);
|
|
|
|
if (rc <= 0)
|
|
|
|
return rc;
|
2013-08-22 15:43:35 +00:00
|
|
|
|
|
|
|
*pid = (pid_t)val;
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
return 1;
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetProcessTime(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
unsigned long long *timestamp)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-08-07 15:30:57 +00:00
|
|
|
*timestamp = 0;
|
2019-07-26 15:27:25 +00:00
|
|
|
return virTypedParamsGetULLong(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_TIME,
|
|
|
|
timestamp);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetSASLUserName(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
const char **username)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
*username = NULL;
|
|
|
|
return virTypedParamsGetString(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SASL_USER_NAME,
|
|
|
|
username);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetX509DName(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
const char **dname)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
*dname = NULL;
|
|
|
|
return virTypedParamsGetString(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_X509_DISTINGUISHED_NAME,
|
|
|
|
dname);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-07 15:30:57 +00:00
|
|
|
/*
|
|
|
|
* Returns: 0 if not present, 1 if present, -1 on error
|
|
|
|
*/
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetSELinuxContext(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
const char **context)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
*context = NULL;
|
|
|
|
return virTypedParamsGetString(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SELINUX_CONTEXT,
|
|
|
|
context);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-19 12:26:17 +00:00
|
|
|
int virIdentityGetSystemToken(virIdentity *ident,
|
|
|
|
const char **token)
|
|
|
|
{
|
|
|
|
*token = NULL;
|
|
|
|
return virTypedParamsGetString(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SYSTEM_TOKEN,
|
|
|
|
token);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetUserName(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
const char *username)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_USER_NAME)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virTypedParamsAddString(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_USER_NAME,
|
|
|
|
username);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetUNIXUserID(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
uid_t uid)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_USER_ID)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
2013-08-22 15:43:35 +00:00
|
|
|
return -1;
|
2019-07-26 15:27:25 +00:00
|
|
|
}
|
2018-07-13 17:55:09 +00:00
|
|
|
|
2019-07-26 15:27:25 +00:00
|
|
|
return virTypedParamsAddULLong(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_USER_ID,
|
|
|
|
uid);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetGroupName(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
const char *groupname)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_GROUP_NAME)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virTypedParamsAddString(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_GROUP_NAME,
|
|
|
|
groupname);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetUNIXGroupID(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
gid_t gid)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_GROUP_ID)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
2013-08-22 15:43:35 +00:00
|
|
|
return -1;
|
2019-07-26 15:27:25 +00:00
|
|
|
}
|
2018-07-13 17:55:09 +00:00
|
|
|
|
2019-07-26 15:27:25 +00:00
|
|
|
return virTypedParamsAddULLong(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_GROUP_ID,
|
|
|
|
gid);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetProcessID(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
pid_t pid)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_ID)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
2013-08-22 15:43:35 +00:00
|
|
|
return -1;
|
2019-07-26 15:27:25 +00:00
|
|
|
}
|
2018-07-13 17:55:09 +00:00
|
|
|
|
2019-07-26 15:27:25 +00:00
|
|
|
return virTypedParamsAddLLong(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_ID,
|
|
|
|
pid);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetProcessTime(virIdentity *ident,
|
2019-07-26 10:59:15 +00:00
|
|
|
unsigned long long timestamp)
|
2013-08-22 15:43:35 +00:00
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_TIME)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
2013-08-22 15:43:35 +00:00
|
|
|
return -1;
|
2019-07-26 15:27:25 +00:00
|
|
|
}
|
2018-07-13 17:55:09 +00:00
|
|
|
|
2019-07-26 15:27:25 +00:00
|
|
|
return virTypedParamsAddULLong(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_TIME,
|
|
|
|
timestamp);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetSASLUserName(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
const char *username)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SASL_USER_NAME)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virTypedParamsAddString(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SASL_USER_NAME,
|
|
|
|
username);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetX509DName(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
const char *dname)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_X509_DISTINGUISHED_NAME)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virTypedParamsAddString(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_X509_DISTINGUISHED_NAME,
|
|
|
|
dname);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetSELinuxContext(virIdentity *ident,
|
2013-08-22 15:43:35 +00:00
|
|
|
const char *context)
|
|
|
|
{
|
2019-07-26 15:27:25 +00:00
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SELINUX_CONTEXT)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virTypedParamsAddString(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SELINUX_CONTEXT,
|
|
|
|
context);
|
2013-08-22 15:43:35 +00:00
|
|
|
}
|
2019-07-26 15:36:29 +00:00
|
|
|
|
|
|
|
|
2020-11-19 12:26:17 +00:00
|
|
|
int virIdentitySetSystemToken(virIdentity *ident,
|
|
|
|
const char *token)
|
|
|
|
{
|
|
|
|
if (virTypedParamsGet(ident->params,
|
|
|
|
ident->nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SYSTEM_TOKEN)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
|
|
_("Identity attribute is already set"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virTypedParamsAddString(&ident->params,
|
|
|
|
&ident->nparams,
|
|
|
|
&ident->maxparams,
|
|
|
|
VIR_CONNECT_IDENTITY_SYSTEM_TOKEN,
|
|
|
|
token);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentitySetParameters(virIdentity *ident,
|
2019-07-26 15:36:29 +00:00
|
|
|
virTypedParameterPtr params,
|
|
|
|
int nparams)
|
|
|
|
{
|
|
|
|
if (virTypedParamsValidate(params, nparams,
|
|
|
|
VIR_CONNECT_IDENTITY_USER_NAME,
|
|
|
|
VIR_TYPED_PARAM_STRING,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_USER_ID,
|
|
|
|
VIR_TYPED_PARAM_ULLONG,
|
|
|
|
VIR_CONNECT_IDENTITY_GROUP_NAME,
|
|
|
|
VIR_TYPED_PARAM_STRING,
|
|
|
|
VIR_CONNECT_IDENTITY_UNIX_GROUP_ID,
|
|
|
|
VIR_TYPED_PARAM_ULLONG,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_ID,
|
|
|
|
VIR_TYPED_PARAM_LLONG,
|
|
|
|
VIR_CONNECT_IDENTITY_PROCESS_TIME,
|
|
|
|
VIR_TYPED_PARAM_ULLONG,
|
|
|
|
VIR_CONNECT_IDENTITY_SASL_USER_NAME,
|
|
|
|
VIR_TYPED_PARAM_STRING,
|
|
|
|
VIR_CONNECT_IDENTITY_X509_DISTINGUISHED_NAME,
|
|
|
|
VIR_TYPED_PARAM_STRING,
|
|
|
|
VIR_CONNECT_IDENTITY_SELINUX_CONTEXT,
|
|
|
|
VIR_TYPED_PARAM_STRING,
|
2020-11-19 12:26:17 +00:00
|
|
|
VIR_CONNECT_IDENTITY_SYSTEM_TOKEN,
|
|
|
|
VIR_TYPED_PARAM_STRING,
|
2019-07-26 15:36:29 +00:00
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
virTypedParamsFree(ident->params, ident->nparams);
|
|
|
|
ident->params = NULL;
|
|
|
|
ident->nparams = 0;
|
|
|
|
ident->maxparams = 0;
|
|
|
|
if (virTypedParamsCopy(&ident->params, params, nparams) < 0)
|
|
|
|
return -1;
|
|
|
|
ident->nparams = nparams;
|
|
|
|
ident->maxparams = nparams;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
int virIdentityGetParameters(virIdentity *ident,
|
2019-07-26 15:36:29 +00:00
|
|
|
virTypedParameterPtr *params,
|
|
|
|
int *nparams)
|
|
|
|
{
|
|
|
|
*params = NULL;
|
|
|
|
*nparams = 0;
|
|
|
|
|
|
|
|
if (virTypedParamsCopy(params, ident->params, ident->nparams) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*nparams = ident->nparams;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|