blob: 4164db662b279090c3f4c118fbf0274f60864669 [file] [log] [blame]
/*****************************************************************************\
* groups.c - Functions to gather group membership information
* These functions utilize a cache for performance reasons
*****************************************************************************
* Copyright (C) 2010 Lawrence Livermore National Security.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Morris Jette <jette1@llnl.gov> et. al.
* CODE-OCEC-09-009. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm 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 General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include "config.h"
/* needed for getgrent_r */
#define _GNU_SOURCE
#include <grp.h>
#include <pthread.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/uid.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "slurm/slurm_errno.h"
#include "groups.h"
/* Pathname of group file record for checking update times */
#ifndef GROUP_FILE
#define GROUP_FILE "/etc/group"
#endif
#define _DEBUG 0
static uid_t *_get_group_members(char *group_name, int *uid_cnt);
static void _cache_del_func(void *x);
static uid_t *_get_group_cache(char *group_name, int *uid_cnt);
static void _log_group_members(char *group_name,
uid_t *group_uids,
int uid_cnt);
static void _put_group_cache(char *group_name, void *group_uids, int uid_cnt);
static list_t *group_cache_list = NULL;
static pthread_mutex_t group_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
struct group_cache_rec {
char *group_name;
int uid_cnt;
uid_t *group_uids;
};
/*
* _uid_cmp
*/
static int _uid_cmp(const void *x, const void *y)
{
uid_t a;
uid_t b;
a = *(uid_t *)x;
b = *(uid_t *)y;
/* Sort in increasing order so that the 0 is at the beginning */
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}
/*
* _remove_duplicate_uids()
*/
static void _remove_duplicate_uids(uid_t **u, int *u_cnt)
{
int j = 0;
uid_t *v;
uid_t cur;
xassert(u);
xassert(u_cnt);
if ((!*u) || (!*u_cnt))
return;
v = xcalloc(*u_cnt, sizeof(uid_t));
qsort(*u, *u_cnt, sizeof(uid_t), _uid_cmp);
cur = (*u)[0];
for (int i = 0; i < *u_cnt; i++) {
if ((*u)[i] == cur)
continue;
v[j++] = cur;
cur = (*u)[i];
}
v[j++] = cur;
xfree(*u);
*u = v;
*u_cnt = j;
}
extern uid_t *get_groups_members(char *group_names, int *user_cnt)
{
uid_t *group_uids = NULL;
char *tmp_names = NULL, *name_ptr = NULL, *one_group_name = NULL;
*user_cnt = 0;
if (group_names == NULL)
return NULL;
tmp_names = xstrdup(group_names);
one_group_name = strtok_r(tmp_names, ",", &name_ptr);
while (one_group_name) {
int tmp_uid_cnt = 0;
uid_t *temp_uids = _get_group_members(one_group_name,
&tmp_uid_cnt);
if (!tmp_uid_cnt) {
xfree(temp_uids);
} else if (!group_uids) {
group_uids = temp_uids;
*user_cnt = tmp_uid_cnt;
} else {
/* concatenate the uid_lists and free the new one */
xrealloc(group_uids,
sizeof(uid_t) * (*user_cnt + tmp_uid_cnt));
for (int i = 0; i < tmp_uid_cnt; i++)
group_uids[(*user_cnt)++] = temp_uids[i];
xfree(temp_uids);
}
one_group_name = strtok_r(NULL, ",", &name_ptr);
}
xfree(tmp_names);
_remove_duplicate_uids(&group_uids, user_cnt);
return group_uids;
}
/*
* _get_group_members - identify the users in a given group name
* IN group_name - a single group name
* OUT uid_cnt - pointer to fill the size of returned array
* RET list of its UIDs or NULL on error
* NOTE: The caller must xfree non-NULL return values
*/
static uid_t *_get_group_members(char *group_name, int *uid_cnt)
{
char *grp_buffer = NULL;
struct group grp, *grp_result = NULL;
struct passwd *pwd_result = NULL;
uid_t *group_uids = NULL, my_uid;
gid_t my_gid;
int buflen = PW_BUF_SIZE, i, res, group_uids_size = 0;
#if defined (__APPLE__)
#else
char pw_buffer[PW_BUF_SIZE];
struct passwd pw;
#endif
*uid_cnt = 0;
group_uids = _get_group_cache(group_name, uid_cnt);
if (*uid_cnt) {
/* We found in cache */
_log_group_members(group_name, group_uids, *uid_cnt);
return group_uids;
}
#if defined(_SC_GETGR_R_SIZE_MAX)
i = sysconf(_SC_GETGR_R_SIZE_MAX);
buflen = MAX(buflen, i);
#endif
grp_buffer = xmalloc(buflen);
while (1) {
errno = 0;
res = getgrnam_r(group_name, &grp, grp_buffer, buflen,
&grp_result);
/* We need to check for !grp_result, since it appears some
* versions of this function do not return an error on
* failure.
*/
if (res != 0 || !grp_result) {
if (errno == ERANGE) {
buflen *= 2;
xrealloc(grp_buffer, buflen);
continue;
}
error("%s: Could not find configured group %s",
__func__, group_name);
xfree(grp_buffer);
return NULL;
}
break;
}
my_gid = grp_result->gr_gid;
/* Get the members from the getgrnam_r() call.
*/
for (i = 0; grp_result->gr_mem[i]; i++) {
if (uid_from_string(grp_result->gr_mem[i], &my_uid) !=
SLURM_SUCCESS) {
continue;
}
if (my_uid == 0)
continue;
if (group_uids_size < (*uid_cnt + 1)) {
group_uids_size += 100;
xrealloc(group_uids, (sizeof(uid_t) * group_uids_size));
}
group_uids[(*uid_cnt)++] = my_uid;
}
/* Note that in environments where user/group enumeration has
* been disabled (typically necessary for large user/group
* databases), the rest of this function essentially does
* nothing. */
#if defined (__APPLE__)
setgrent();
while (1) {
if ((grp_result = getgrent()) == NULL)
break;
#else
setgrent();
while (1) {
/* MH-CEA workaround to handle different group entries with
* the same gid
*/
errno = 0;
res = getgrent_r(&grp, grp_buffer, buflen, &grp_result);
if (res != 0 || grp_result == NULL) {
/* FreeBSD returns 0 and sets the grp_result to NULL
* unlike linux which returns ENOENT.
*/
if (errno == ERANGE) {
buflen *= 2;
xrealloc(grp_buffer, buflen);
continue;
}
break;
}
#endif
if (grp_result->gr_gid == my_gid) {
if (xstrcmp(grp_result->gr_name, group_name)) {
debug("including members of group '%s' as it "
"corresponds to the same gid as group"
" '%s'",grp_result->gr_name,group_name);
}
for (i=0; grp_result->gr_mem[i]; i++) {
if (uid_from_string(grp_result->gr_mem[i],
&my_uid) != SLURM_SUCCESS) {
/* Group member without valid login */
continue;
}
if (my_uid == 0)
continue;
if (group_uids_size < (*uid_cnt + 1)) {
group_uids_size += 100;
xrealloc(group_uids, (sizeof(uid_t) *
group_uids_size));
}
group_uids[(*uid_cnt)++] = my_uid;
}
}
}
endgrent();
setpwent();
#if defined (__APPLE__)
while ((pwd_result = getpwent()) != NULL) {
#else
while (!getpwent_r(&pw, pw_buffer, PW_BUF_SIZE, &pwd_result)) {
#endif
/* At eof FreeBSD returns 0 unlike Linux
* which returns ENOENT.
*/
if (pwd_result == NULL)
break;
if (pwd_result->pw_gid != my_gid)
continue;
if (group_uids_size < (*uid_cnt + 1)) {
group_uids_size += 100;
xrealloc(group_uids, (sizeof(uid_t) * group_uids_size));
}
group_uids[(*uid_cnt)++] = pwd_result->pw_uid;
}
endpwent();
xfree(grp_buffer);
_put_group_cache(group_name, group_uids, *uid_cnt);
_log_group_members(group_name, group_uids, *uid_cnt);
if (!(*uid_cnt))
xfree(group_uids);
return group_uids;
}
/* Delete our group/uid cache */
extern void clear_group_cache(void)
{
slurm_mutex_lock(&group_cache_mutex);
FREE_NULL_LIST(group_cache_list);
slurm_mutex_unlock(&group_cache_mutex);
}
/* get_group_tlm - return the time of last modification for the GROUP_FILE */
extern time_t get_group_tlm(void)
{
struct stat stat_buf;
if (stat(GROUP_FILE, &stat_buf)) {
error("Can't stat file %s %m", GROUP_FILE);
return (time_t) 0;
}
return stat_buf.st_mtime;
}
/* Get a record from our group/uid cache.
* Return NULL if not found. */
static uid_t *_get_group_cache(char *group_name, int *uid_cnt)
{
list_itr_t *iter;
struct group_cache_rec *cache_rec;
uid_t *group_uids = NULL;
int sz;
*uid_cnt = 0;
slurm_mutex_lock(&group_cache_mutex);
if (!group_cache_list) {
slurm_mutex_unlock(&group_cache_mutex);
return NULL;
}
iter = list_iterator_create(group_cache_list);
while ((cache_rec = list_next(iter))) {
if (xstrcmp(group_name, cache_rec->group_name))
continue;
sz = sizeof(uid_t) * cache_rec->uid_cnt;
group_uids = xmalloc(sz);
memcpy(group_uids, cache_rec->group_uids, sz);
*uid_cnt = cache_rec->uid_cnt;
break;
}
list_iterator_destroy(iter);
slurm_mutex_unlock(&group_cache_mutex);
return group_uids;
}
/* Delete a record from the group/uid cache, used by list functions */
static void _cache_del_func(void *x)
{
struct group_cache_rec *cache_rec;
cache_rec = (struct group_cache_rec *) x;
xfree(cache_rec->group_name);
xfree(cache_rec->group_uids);
xfree(cache_rec);
}
/* Put a record on our group/uid cache */
static void _put_group_cache(char *group_name, void *group_uids, int uid_cnt)
{
struct group_cache_rec *cache_rec;
int sz;
slurm_mutex_lock(&group_cache_mutex);
if (!group_cache_list) {
group_cache_list = list_create(_cache_del_func);
}
sz = sizeof(uid_t) * (uid_cnt);
cache_rec = xmalloc(sizeof(struct group_cache_rec));
cache_rec->group_name = xstrdup(group_name);
cache_rec->uid_cnt = uid_cnt;
cache_rec->group_uids = xmalloc(sizeof(uid_t) + sz);
if (uid_cnt > 0)
memcpy(cache_rec->group_uids, group_uids, sz);
list_append(group_cache_list, cache_rec);
slurm_mutex_unlock(&group_cache_mutex);
}
static void _log_group_members(char *group_name, uid_t *group_uids, int uid_cnt)
{
#if _DEBUG
if ((!group_uids) || (!uid_cnt)) {
info("Group %s has no users", group_name);
return;
}
info("Group %s contains uids:", group_name);
for (int i = 0; i < uid_cnt; i++)
info(" %u", group_uids[i]);
#endif
}