|  | /*****************************************************************************\ | 
|  | *  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 | 
|  | } |