/*****************************************************************************\ * nss_slurm.c - Slurm NSS Plugin ***************************************************************************** * Copyright (C) 2018-2019 SchedMD LLC. * Written by Tim Wickberg * * This file is part of Slurm, a resource management program. * For details, see . * 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 #include #include #include #include #include #include #include #include #include #include "slurm/slurm.h" #include "src/common/slurm_xlator.h" #include "src/common/parse_config.h" #include "src/common/stepd_api.h" /* * One important design note: we cannot load the slurm.conf file using the * usual API calls, as it will internally trigger various UID/GID lookups, * which will then in turn end up back inside this library. At which point, * we'll end up deadlocked on an internal Slurm mutex. */ static char node[HOST_NAME_MAX] = ""; static char spool[PATH_MAX] = DEFAULT_SPOOLDIR; static int _load_config(void) { static int config_loaded = -1; struct stat statbuf; s_p_options_t options[] = { {"SlurmdSpoolDir", S_P_STRING}, {"NodeName", S_P_STRING}, {NULL} }; s_p_hashtbl_t *tbl = NULL; char *conf = "/etc/nss_slurm.conf"; char *tmp = NULL; if (config_loaded != -1) return config_loaded; tbl = s_p_hashtbl_create(options); if (stat(conf, &statbuf) || !statbuf.st_size) { /* No file, continue below to set defaults */ } else if (s_p_parse_file(tbl, NULL, conf, false)) { /* could not load or parse file */ return (config_loaded = SLURM_ERROR); } /* * For the node name: * 1) Use the config value, or * 2) Use SLURMD_NODENAME env var, * 3) Use gethostname(), and chop off the domain. */ if (s_p_get_string(&tmp, "NodeName", tbl)) { strlcpy(node, tmp, HOST_NAME_MAX); xfree(tmp); } else if ((tmp = getenv("SLURMD_NODENAME"))) { strlcpy(node, tmp, HOST_NAME_MAX); } else { char *dot; if (gethostname(node, HOST_NAME_MAX)) goto error; if ((dot = strchr(node, '.'))) *dot = '\0'; } if (s_p_get_string(&tmp, "SlurmdSpoolDir", tbl)) { strlcpy(spool, tmp, PATH_MAX); xfree(tmp); } s_p_hashtbl_destroy(tbl); return (config_loaded = SLURM_SUCCESS); error: s_p_hashtbl_destroy(tbl); return (config_loaded = SLURM_ERROR); } static struct passwd *_pw_internal(int mode, uid_t uid, const char *name) { List steps = NULL; ListIterator itr = NULL; step_loc_t *stepd; int fd; struct passwd *pwd = NULL; if (_load_config()) return NULL; /* * Both arguments to stepd_available() must be provided, otherwise * it will internally try to load the Slurm config to sort it out. */ if (!(steps = stepd_available(spool, node))) { fprintf(stderr, "error retrieving Slurm step info\n"); return NULL; } itr = list_iterator_create(steps); while ((stepd = list_next(itr))) { fd = stepd_connect(stepd->directory, stepd->nodename, stepd->jobid, stepd->stepid, &stepd->protocol_version); if (fd < 0) continue; pwd = stepd_getpw(fd, stepd->protocol_version, mode, uid, name); close(fd); if (pwd) break; } list_iterator_destroy(itr); FREE_NULL_LIST(steps); return pwd; } static int _internal_getpw(int mode, uid_t uid, const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { int len_name, len_passwd, len_gecos, len_dir, len_shell; struct passwd *rpc_result = NULL; if (!(rpc_result = _pw_internal(mode, uid, name))) return NSS_STATUS_NOTFOUND; len_name = strlen(rpc_result->pw_name); len_passwd = strlen(rpc_result->pw_passwd); len_gecos = strlen(rpc_result->pw_gecos); len_dir = strlen(rpc_result->pw_dir); len_shell = strlen(rpc_result->pw_shell); /* need space for an extra five NUL characters */ if ((len_name + len_passwd + len_gecos + len_dir + len_shell + 5) > buflen) { xfree_struct_passwd(rpc_result); return ERANGE; } strncpy(buf, rpc_result->pw_name, len_name + 1); pwd->pw_name = buf; buf += len_name + 1; strncpy(buf, rpc_result->pw_passwd, len_passwd + 1); pwd->pw_passwd = buf; buf += len_passwd + 1; pwd->pw_uid = rpc_result->pw_uid; pwd->pw_gid = rpc_result->pw_gid; strncpy(buf, rpc_result->pw_gecos, len_gecos + 1); pwd->pw_gecos = buf; buf += len_gecos + 1; strncpy(buf, rpc_result->pw_dir, len_dir + 1); pwd->pw_dir = buf; buf += len_dir + 1; strncpy(buf, rpc_result->pw_shell, len_shell + 1); pwd->pw_shell = buf; *result = pwd; xfree_struct_passwd(rpc_result); return NSS_STATUS_SUCCESS; } int _nss_slurm_getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { return _internal_getpw(GETPW_MATCH_USER_AND_PID, NO_VAL, name, pwd, buf, buflen, result); } int _nss_slurm_getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { return _internal_getpw(GETPW_MATCH_USER_AND_PID, uid, NULL, pwd, buf, buflen, result); } static int entry_fetched = 1; int _nss_slurm_setpwent(void) { entry_fetched = 0; return NSS_STATUS_SUCCESS; } int _nss_slurm_getpwent_r(struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { /* * There is only ever one entry here. The docs indicate we should * return NSS_STATUS_NOTFOUND on successive queries. */ if (entry_fetched) return NSS_STATUS_NOTFOUND; entry_fetched = 1; return _internal_getpw(GETPW_MATCH_PID, NO_VAL, NULL, pwd, buf, buflen, result); } int _nss_slurm_endpwent(void) { return NSS_STATUS_SUCCESS; } static struct group **_gr_internal(int mode, gid_t gid, const char *name) { List steps = NULL; ListIterator itr = NULL; step_loc_t *stepd; int fd; struct group **grps = NULL; if (_load_config()) return NULL; /* * Both arguments to stepd_available() must be provided, otherwise * it will internally try to load the Slurm config to sort it out. */ if (!(steps = stepd_available(spool, node))) { fprintf(stderr, "error retrieving Slurm step info\n"); return NULL; } itr = list_iterator_create(steps); while ((stepd = list_next(itr))) { fd = stepd_connect(stepd->directory, stepd->nodename, stepd->jobid, stepd->stepid, &stepd->protocol_version); if (fd < 0) continue; grps = stepd_getgr(fd, stepd->protocol_version, mode, gid, name); close(fd); if (grps) break; } list_iterator_destroy(itr); FREE_NULL_LIST(steps); return grps; } static int next_gr_entry = 0; static struct group **gr_rpc_results = NULL; static int _internal_getgr(int mode, gid_t gid, const char *name, struct group *grp, char *buf, size_t buflen, struct group **result) { int len_name, len_passwd, len_mem = 0; int i = 0; /* * GETGR_MATCH_PID is used by getgrent, and can have multiple results * to iterate through. next_gr_entry > 0 indicates we've already * fetched those records, and just need to continue returning them. */ if ((mode == GETGR_MATCH_PID) && (next_gr_entry > 0)) { if (!gr_rpc_results || !gr_rpc_results[next_gr_entry]) { /* No records left, must have finished */ return NSS_STATUS_NOTFOUND; } /* Fetching next record */ i = next_gr_entry; } else if (!(gr_rpc_results = _gr_internal(mode, gid, name))) return NSS_STATUS_NOTFOUND; len_name = strlen(gr_rpc_results[i]->gr_name); len_passwd = strlen(gr_rpc_results[i]->gr_passwd); /* * In the current implementation only a single member is returned. */ if (gr_rpc_results[i]->gr_mem) len_mem = strlen(gr_rpc_results[i]->gr_mem[0]); /* * Need space for an extra 3 NUL characters. * Plus space for the (char *) array for gr_mem, which we're * simplifying as (3 * sizeof(char *)) to account for potential buffer * alignment issues. */ if ((len_name + len_passwd + len_mem + 3) > buflen) { xfree_struct_group_array(gr_rpc_results); return ERANGE; } strncpy(buf, gr_rpc_results[i]->gr_name, len_name + 1); grp->gr_name = buf; buf += len_name + 1; strncpy(buf, gr_rpc_results[i]->gr_passwd, len_passwd + 1); grp->gr_passwd = buf; buf += len_passwd + 1; grp->gr_gid = gr_rpc_results[i]->gr_gid; if (gr_rpc_results[i]->gr_mem) { char *gr_mem_ptr = buf; char **mem_array_start; strncpy(buf, gr_rpc_results[i]->gr_mem[0], len_mem + 1); buf += len_mem + 1; /* * Storing a NULL-terminated array of (char **) into a (char *) * is awkward due to the alignment issues. The casting tricks * below attempt to handle this. * * As the current implementation only returns a single group * member, we have only two elements to populate - the actual * member, plus the NULL termination. The calculation for space * required is (3 * sizeof(char *)) to account for possible * mis-alignment of the buffer, which we resolve by skipping * ahead of the last buffer position by sizeof(char *) and * letting the (char **) cast then round down into the previous * word somewhere. */ mem_array_start = (char **) buf + sizeof(char *); mem_array_start[0] = gr_mem_ptr; mem_array_start[1] = NULL; grp->gr_mem = mem_array_start; } else grp->gr_mem = NULL; *result = grp; if (mode != GETGR_MATCH_PID) { xfree_struct_group_array(gr_rpc_results); gr_rpc_results = NULL; } else { next_gr_entry++; if (!gr_rpc_results[next_gr_entry]) { xfree_struct_group_array(gr_rpc_results); gr_rpc_results = NULL; } } return NSS_STATUS_SUCCESS; } int _nss_slurm_getgrnam_r(const char *name, struct group *pwd, char *buf, size_t buflen, struct group **result) { return _internal_getgr(GETGR_MATCH_GROUP_AND_PID, NO_VAL, name, pwd, buf, buflen, result); } int _nss_slurm_getgrgid_r(gid_t gid, struct group *pwd, char *buf, size_t buflen, struct group **result) { return _internal_getgr(GETGR_MATCH_GROUP_AND_PID, gid, NULL, pwd, buf, buflen, result); } int _nss_slurm_setgrent(void) { xfree_struct_group_array(gr_rpc_results); gr_rpc_results = NULL; next_gr_entry = 0; return NSS_STATUS_SUCCESS; } int _nss_slurm_getgrent_r(struct group *grp, char *buf, size_t buflen, struct group **result) { return _internal_getgr(GETGR_MATCH_PID, NO_VAL, NULL, grp, buf, buflen, result); } int _nss_slurm_endgrent(void) { xfree_struct_group_array(gr_rpc_results); gr_rpc_results = NULL; next_gr_entry = 0; return NSS_STATUS_SUCCESS; }