| /*****************************************************************************\ |
| * nss_slurm.c - Slurm NSS Plugin |
| ***************************************************************************** |
| * Copyright (C) 2018-2019 SchedMD LLC. |
| * Written by Tim Wickberg <tim@schedmd.com> |
| * |
| * 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 <fcntl.h> |
| #include <grp.h> |
| #include <limits.h> |
| #include <netdb.h> |
| #include <nss.h> |
| #include <pwd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "slurm/slurm.h" |
| #include "src/common/slurm_xlator.h" |
| |
| #include "src/common/parse_config.h" |
| #include "src/common/read_config.h" |
| #include "src/common/stepd_api.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.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, 0, NULL)) { |
| /* 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)) { |
| /* |
| * Perform node wildcard substitution. Cannot use |
| * slurm_conf_expand_slurmd_path() since that requires |
| * slurm.conf be loaded which cannot be done safely |
| * in nss_slurm. |
| */ |
| xstrsubstitute(tmp, "%n", node); |
| 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 hostent *_host_internal(int mode, const char *nodename) |
| { |
| list_t *steps = NULL; |
| list_itr_t *itr = NULL; |
| step_loc_t *stepd; |
| int fd; |
| struct hostent *host = 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->step_id, &stepd->protocol_version); |
| |
| if (fd < 0) |
| continue; |
| |
| host = stepd_gethostbyname(fd, stepd->protocol_version, mode, |
| nodename); |
| close(fd); |
| if (host) |
| break; |
| } |
| list_iterator_destroy(itr); |
| FREE_NULL_LIST(steps); |
| |
| return host; |
| } |
| |
| static int _internal_gethost(int af, const char *name, char *buf, |
| size_t buflen, struct hostent *result, int *errnop) |
| { |
| int len_name, len_aliases = 0, cnt_aliases = 0, len_addr; |
| struct hostent *rpc_result = NULL; |
| int i, mode = 0; |
| |
| if (af == AF_INET) |
| mode = GETHOST_IPV4; |
| else if (af == AF_INET6) |
| mode = GETHOST_IPV6; |
| else if (af == AF_UNSPEC) |
| mode = GETHOST_IPV4 | GETHOST_IPV6; |
| |
| if (!(rpc_result = _host_internal(mode, name))) |
| return NSS_STATUS_NOTFOUND; |
| |
| len_name = strlen(rpc_result->h_name); |
| len_aliases = sizeof(char*); |
| for (i = 0; rpc_result->h_aliases[i]; i++) { |
| len_aliases += strlen(rpc_result->h_aliases[i]); |
| cnt_aliases++; |
| } |
| len_addr = rpc_result->h_length; |
| |
| if ((len_name + len_aliases + ((cnt_aliases + 1) * sizeof(char*)) + |
| len_addr + (2 * sizeof(char *)) + cnt_aliases + 1) > buflen) { |
| xfree_struct_hostent(rpc_result); |
| *errnop = ERANGE; |
| return NSS_STATUS_TRYAGAIN; |
| } |
| |
| strncpy(buf, rpc_result->h_name, len_name + 1); |
| result->h_name = buf; |
| buf += len_name + 1; |
| |
| result->h_aliases = (char **) buf; |
| buf += ((cnt_aliases + 1) * sizeof(char*)); |
| |
| for (i = 0; i < cnt_aliases; i++) { |
| len_aliases = strlen(rpc_result->h_aliases[i]); |
| strncpy(buf, rpc_result->h_aliases[i], len_aliases + 1); |
| result->h_aliases[i] = buf; |
| buf += len_aliases + 1; |
| } |
| result->h_aliases[cnt_aliases] = NULL; |
| |
| result->h_addrtype = rpc_result->h_addrtype; |
| result->h_length = rpc_result->h_length; |
| |
| result->h_addr_list = (char **) buf; |
| buf += (2 * sizeof(char*)); |
| memcpy(buf, rpc_result->h_addr_list[0], len_addr); |
| result->h_addr_list[0] = buf; |
| result->h_addr_list[1] = NULL; |
| |
| xfree_struct_hostent(rpc_result); |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| enum nss_status _nss_slurm_gethostbyname_r(const char *name, |
| struct hostent *result, char *buf, |
| size_t buflen, int *errnop, |
| int *herrnop) |
| { |
| return _internal_gethost(AF_UNSPEC, name, buf, buflen, result, errnop); |
| } |
| |
| enum nss_status _nss_slurm_gethostbyname2_r(const char *name, int af, |
| struct hostent *result, |
| char *buf, size_t buflen, |
| int *errnop, int *herrnop) |
| { |
| return _internal_gethost(af, name, buf, buflen, result, errnop); |
| } |
| |
| static struct passwd *_pw_internal(int mode, uid_t uid, const char *name) |
| { |
| list_t *steps = NULL; |
| list_itr_t *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->step_id, &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, |
| int *errnop) |
| { |
| 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); |
| *errnop = ERANGE; |
| return NSS_STATUS_TRYAGAIN; |
| } |
| |
| 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; |
| |
| xfree_struct_passwd(rpc_result); |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| enum nss_status _nss_slurm_getpwnam_r(const char *name, struct passwd *pwd, |
| char *buf, size_t buflen, int *errnop) |
| { |
| return _internal_getpw(GETPW_MATCH_USER_AND_PID, NO_VAL, name, pwd, |
| buf, buflen, errnop); |
| } |
| |
| enum nss_status _nss_slurm_getpwuid_r(uid_t uid, struct passwd *pwd, |
| char *buf, size_t buflen, int *errnop) |
| { |
| return _internal_getpw(GETPW_MATCH_USER_AND_PID, uid, NULL, pwd, |
| buf, buflen, errnop); |
| } |
| |
| static int entry_fetched = 1; |
| |
| int _nss_slurm_setpwent(void) |
| { |
| entry_fetched = 0; |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| enum nss_status _nss_slurm_getpwent_r(struct passwd *pwd, char *buf, |
| size_t buflen, int *errnop) |
| { |
| /* |
| * 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, |
| errnop); |
| } |
| |
| enum nss_status _nss_slurm_endpwent(void) |
| { |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| static struct group **_gr_internal(int mode, gid_t gid, const char *name) |
| { |
| list_t *steps = NULL; |
| list_itr_t *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->step_id, &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, |
| int *errnop) |
| { |
| 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); |
| *errnop = ERANGE; |
| return NSS_STATUS_TRYAGAIN; |
| } |
| |
| 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 |
| * misalignment 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; |
| |
| 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; |
| } |
| |
| enum nss_status _nss_slurm_getgrnam_r(const char *name, struct group *pwd, |
| char *buf, size_t buflen, int *errnop) |
| { |
| return _internal_getgr(GETGR_MATCH_GROUP_AND_PID, NO_VAL, name, pwd, |
| buf, buflen, errnop); |
| |
| } |
| |
| enum nss_status _nss_slurm_getgrgid_r(gid_t gid, struct group *pwd, |
| char *buf, size_t buflen, int *errnop) |
| { |
| return _internal_getgr(GETGR_MATCH_GROUP_AND_PID, gid, NULL, pwd, |
| buf, buflen, errnop); |
| } |
| |
| enum nss_status _nss_slurm_setgrent(void) |
| { |
| xfree_struct_group_array(gr_rpc_results); |
| gr_rpc_results = NULL; |
| next_gr_entry = 0; |
| |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| enum nss_status _nss_slurm_getgrent_r(struct group *grp, char *buf, |
| size_t buflen, int *errnop) |
| { |
| return _internal_getgr(GETGR_MATCH_PID, NO_VAL, NULL, grp, buf, buflen, |
| errnop); |
| } |
| |
| enum nss_status _nss_slurm_endgrent(void) |
| { |
| xfree_struct_group_array(gr_rpc_results); |
| gr_rpc_results = NULL; |
| next_gr_entry = 0; |
| |
| return NSS_STATUS_SUCCESS; |
| } |