| /*****************************************************************************\ |
| * pam_slurm.c |
| ***************************************************************************** |
| * Copyright (C) 2002-2007 The Regents of the University of California. |
| * Copyright (C) 2008-2009 Lawrence Livermore National Security. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * UCRL-CODE-2002-040. |
| * |
| * Written by Chris Dunlap <cdunlap@llnl.gov> |
| * and Jim Garlick <garlick@llnl.gov> |
| * modified for Slurm by Moe Jette <jette@llnl.gov>. |
| * |
| * This file is part of pam_slurm, a PAM module for restricting access to |
| * the compute nodes within a cluster based on information obtained from |
| * Simple Linux Utility for Resource Management (Slurm). For details, see |
| * <http://www.llnl.gov/linux/slurm/>. |
| * |
| * pam_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. |
| * |
| * pam_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 pam_slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #if HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <ctype.h> |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <pwd.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| |
| #include "slurm/slurm.h" |
| #include "src/common/read_config.h" |
| #include "src/common/xmalloc.h" |
| |
| /* Define the externally visible functions in this file. |
| */ |
| #define PAM_SM_ACCOUNT |
| #include <security/pam_modules.h> |
| #include <security/_pam_macros.h> |
| |
| |
| struct _options { |
| int disable_sys_info; |
| int enable_debug; |
| int enable_silence; |
| const char *msg_prefix; |
| const char *msg_suffix; |
| }; |
| |
| /* Define the functions to be called before and after load since _init |
| * and _fini are obsolete, and their use can lead to unpredictable |
| * results. |
| */ |
| void __attribute__ ((constructor)) libpam_slurm_init(void); |
| void __attribute__ ((destructor)) libpam_slurm_fini(void); |
| |
| /* |
| * Handle for libslurm.so |
| * |
| * We open libslurm.so via dlopen () in order to pass the |
| * flag RTDL_GLOBAL so that subsequently loaded modules have |
| * access to libslurm symbols. This is pretty much only needed |
| * for dynamically loaded modules that would otherwise be |
| * linked against libslurm. |
| * |
| */ |
| static void * slurm_h = NULL; |
| static int pam_debug = 0; |
| |
| static void _log_msg(int level, const char *format, ...); |
| static void _parse_args(struct _options *opts, int argc, const char **argv); |
| static int _hostrange_member(char *hostname, char *str); |
| static int _slurm_match_allocation(uid_t uid); |
| static void _send_denial_msg(pam_handle_t *pamh, struct _options *opts, |
| const char *user, uid_t uid); |
| |
| #define DBG(msg,args...) \ |
| do { \ |
| if (pam_debug) \ |
| _log_msg(LOG_INFO, msg, ##args); \ |
| } while (0) |
| |
| /**********************************\ |
| * Account Management Functions * |
| \**********************************/ |
| |
| PAM_EXTERN int |
| pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) |
| { |
| struct _options opts; |
| int retval; |
| char *user; |
| void *dummy; /* needed to eliminate warning: |
| * dereferencing type-punned pointer will break |
| * strict-aliasing rules */ |
| struct passwd *pw; |
| uid_t uid; |
| int auth = PAM_PERM_DENIED; |
| |
| _parse_args(&opts, argc, argv); |
| if (flags & PAM_SILENT) |
| opts.enable_silence = 1; |
| |
| retval = pam_get_item(pamh, PAM_USER, (const void **) &dummy); |
| user = (char *) dummy; |
| if ((retval != PAM_SUCCESS) || (user == NULL) || (*user == '\0')) { |
| _log_msg(LOG_ERR, "unable to identify user: %s", |
| pam_strerror(pamh, retval)); |
| return(PAM_USER_UNKNOWN); |
| } |
| if (!(pw = getpwnam(user))) { |
| _log_msg(LOG_ERR, "user %s does not exist", user); |
| return(PAM_USER_UNKNOWN); |
| } |
| uid = pw->pw_uid; |
| |
| if (uid == 0) |
| auth = PAM_SUCCESS; |
| else if (_slurm_match_allocation(uid)) |
| auth = PAM_SUCCESS; |
| |
| if ((auth != PAM_SUCCESS) && (!opts.enable_silence)) |
| _send_denial_msg(pamh, &opts, user, uid); |
| |
| /* |
| * Generate an entry to the system log if access was |
| * denied (!PAM_SUCCESS) or disable_sys_info is not set |
| */ |
| if ((auth != PAM_SUCCESS) || (!opts.disable_sys_info)) { |
| _log_msg(LOG_INFO, "access %s for user %s (uid=%u)", |
| (auth == PAM_SUCCESS) ? "granted" : "denied", |
| user, uid); |
| } |
| |
| return(auth); |
| } |
| |
| |
| /************************\ |
| * Internal Functions * |
| \************************/ |
| |
| /* |
| * Writes message described by the 'format' string to syslog. |
| */ |
| static void |
| _log_msg(int level, const char *format, ...) |
| { |
| va_list args; |
| |
| openlog("pam_slurm", LOG_CONS | LOG_PID, LOG_AUTHPRIV); |
| va_start(args, format); |
| vsyslog(level, format, args); |
| va_end(args); |
| closelog(); |
| return; |
| } |
| |
| /* |
| * pam 1.5.3 stopped providing _pam_drop_reply(). Our use does not currently |
| * fetch sensitive data so simply free this structure. |
| */ |
| static void _pam_slurm_drop_response(struct pam_response *reply, int replies) |
| { |
| for (int i = 0; i < replies; i++) { |
| if (reply[i].resp) |
| free(reply[i].resp); |
| } |
| free(reply); |
| } |
| |
| /* |
| * Parses module args passed via PAM's config. |
| */ |
| static void |
| _parse_args(struct _options *opts, int argc, const char **argv) |
| { |
| int i; |
| |
| opts->disable_sys_info = 0; |
| opts->enable_debug = 0; |
| opts->enable_silence = 0; |
| opts->msg_prefix = ""; |
| opts->msg_suffix = ""; |
| |
| /* rsh_kludge: |
| * The rsh service under RH71 (rsh-0.17-2.5) truncates the first char |
| * of this msg. The rsh client sends 3 NUL-terminated ASCII strings: |
| * client-user-name, server-user-name, and command string. The server |
| * then validates the user. If the user is valid, it responds with a |
| * 1-byte zero; o/w, it responds with a 1-byte one followed by an ASCII |
| * error message and a newline. RH's server is using the default PAM |
| * conversation function which doesn't prepend the message with a |
| * single-byte error code. As a result, the client receives a string, |
| * interprets the first byte as a non-zero status, and treats the |
| * remaining string as an error message. The rsh_kludge prepends a |
| * newline which will be interpreted by the rsh client as an |
| * error status. |
| * |
| * rlogin_kludge: |
| * The rlogin service under RH71 (rsh-0.17-2.5) does not perform a |
| * carriage-return after the PAM error message is displayed |
| * which results |
| * in the "staircase-effect" of the next message. The rlogin_kludge |
| * appends a carriage-return to prevent this. |
| */ |
| for (i=0; i<argc; i++) { |
| if (!strcmp(argv[i], "debug")) |
| opts->enable_debug = pam_debug = 1; |
| else if (!strcmp(argv[i], "no_sys_info")) |
| opts->disable_sys_info = 1; |
| else if (!strcmp(argv[i], "no_warn")) |
| opts->enable_silence = 1; |
| else if (!strcmp(argv[i], "rsh_kludge")) |
| opts->msg_prefix = "\n"; |
| else if (!strcmp(argv[i], "rlogin_kludge")) |
| opts->msg_suffix = "\r"; |
| else |
| _log_msg(LOG_ERR, "unknown option [%s]", argv[i]); |
| } |
| return; |
| } |
| |
| /* |
| * Return 1 if 'hostname' is a member of 'str', a Slurm-style host list as |
| * returned by Slurm database queries, else 0. The 'str' argument is |
| * truncated to the base prefix as a side-effect. |
| */ |
| static int |
| _hostrange_member(char *hostname, char *str) |
| { |
| hostlist_t *hl; |
| int found_host; |
| |
| if (!*hostname || !*str) |
| return 0; |
| |
| if ((hl = slurm_hostlist_create(str)) == NULL) |
| return 0; |
| found_host = slurm_hostlist_find(hl, hostname); |
| slurm_hostlist_destroy(hl); |
| |
| if (found_host == -1) |
| return 0; |
| else |
| return 1; |
| } |
| |
| /* _gethostname_short - equivalent to gethostname, but return only the first |
| * component of the fully qualified name |
| * (e.g. "linux123.foo.bar" becomes "linux123") |
| * |
| * Copied from src/common/read_config.c because it is not exported |
| * through libslurm. |
| * |
| * OUT name |
| */ |
| static int |
| _gethostname_short (char *name, size_t len) |
| { |
| int error_code, name_len; |
| char *dot_ptr, path_name[1024]; |
| |
| error_code = gethostname(path_name, sizeof(path_name)); |
| if (error_code) |
| return error_code; |
| |
| dot_ptr = strchr (path_name, '.'); |
| if (dot_ptr == NULL) |
| dot_ptr = path_name + strlen(path_name); |
| else |
| dot_ptr[0] = '\0'; |
| |
| name_len = (dot_ptr - path_name); |
| if (name_len > len) |
| return ENAMETOOLONG; |
| |
| strcpy(name, path_name); |
| return 0; |
| } |
| |
| |
| /* |
| * Query the Slurm database to find out if 'uid' has been allocated |
| * this node. If so, return 1 indicating that 'uid' is authorized to |
| * this node else return 0. |
| */ |
| static int |
| _slurm_match_allocation(uid_t uid) |
| { |
| int authorized = 0, i; |
| char hostname[HOST_NAME_MAX]; |
| char *nodename = NULL; |
| job_info_msg_t * msg; |
| |
| slurm_init(NULL); |
| |
| if (_gethostname_short(hostname, sizeof(hostname)) < 0) { |
| _log_msg(LOG_ERR, "gethostname: %m"); |
| return 0; |
| } |
| |
| if (!(nodename = slurm_conf_get_nodename(hostname))) { |
| if (!(nodename = slurm_conf_get_aliased_nodename())) { |
| /* if no match, try localhost (Should only be |
| * valid in a test environment) */ |
| if (!(nodename = |
| slurm_conf_get_nodename("localhost"))) { |
| _log_msg(LOG_ERR, |
| "slurm_conf_get_aliased_nodename: " |
| "no hostname found"); |
| return 0; |
| } |
| } |
| } |
| |
| DBG ("does uid %ld have \"%s\" allocated?", uid, nodename); |
| |
| if (slurm_load_job_user(&msg, uid, SHOW_ALL) < 0) { |
| _log_msg(LOG_ERR, "slurm_load_job_user: %s", |
| slurm_strerror(errno)); |
| return 0; |
| } |
| |
| DBG ("slurm_load_jobs returned %d records", msg->record_count); |
| |
| for (i = 0; i < msg->record_count; i++) { |
| job_info_t *j = &msg->job_array[i]; |
| |
| if (j->job_state == JOB_RUNNING) { |
| |
| DBG ("jobid %ld: nodes=\"%s\"", j->job_id, j->nodes); |
| |
| if (_hostrange_member(nodename, j->nodes) ) { |
| DBG ("user %ld allocated node %s in job %ld", |
| uid, nodename, j->job_id); |
| authorized = 1; |
| break; |
| } else { |
| char *nodename; |
| nodename = slurm_conf_get_nodename(hostname); |
| if (nodename) { |
| if (_hostrange_member(nodename, |
| j->nodes)) { |
| DBG ("user %ld allocated node %s in job %ld", |
| uid, nodename, j->job_id); |
| authorized = 1; |
| xfree(nodename); |
| break; |
| } |
| xfree(nodename); |
| } |
| } |
| } |
| } |
| xfree(nodename); |
| slurm_free_job_info_msg (msg); |
| |
| return authorized; |
| } |
| |
| /* |
| * Sends a message to the application informing the user |
| * that access was denied due to Slurm. |
| */ |
| static void |
| _send_denial_msg(pam_handle_t *pamh, struct _options *opts, |
| const char *user, uid_t uid) |
| { |
| int retval; |
| struct pam_conv *conv; |
| void *dummy; /* needed to eliminate warning: |
| * dereferencing type-punned pointer will |
| * break strict-aliasing rules */ |
| int n; |
| char str[PAM_MAX_MSG_SIZE]; |
| struct pam_message msg[1]; |
| const struct pam_message *pmsg[1]; |
| struct pam_response *prsp; |
| |
| /* Get conversation function to talk with app. |
| */ |
| retval = pam_get_item(pamh, PAM_CONV, (const void **) &dummy); |
| conv = (struct pam_conv *) dummy; |
| if (retval != PAM_SUCCESS) { |
| _log_msg(LOG_ERR, "unable to get pam_conv: %s", |
| pam_strerror(pamh, retval)); |
| return; |
| } |
| |
| /* Construct msg to send to app. |
| */ |
| n = snprintf(str, sizeof(str), |
| "%sAccess denied: user %s (uid=%u) has no active jobs on this node.%s", |
| opts->msg_prefix, user, uid, opts->msg_suffix); |
| if ((n < 0) || (n >= sizeof(str))) |
| _log_msg(LOG_ERR, "exceeded buffer for pam_conv message"); |
| msg[0].msg_style = PAM_ERROR_MSG; |
| msg[0].msg = str; |
| pmsg[0] = &msg[0]; |
| prsp = NULL; |
| |
| /* Send msg to app and free the (meaningless) rsp. |
| */ |
| retval = conv->conv(1, pmsg, &prsp, conv->appdata_ptr); |
| if (retval != PAM_SUCCESS) |
| _log_msg(LOG_ERR, "unable to converse with app: %s", |
| pam_strerror(pamh, retval)); |
| if (prsp != NULL) |
| _pam_slurm_drop_response(prsp, 1); |
| |
| return; |
| } |
| |
| /* |
| * Dynamically open system's libslurm.so with RTLD_GLOBAL flag. |
| * This allows subsequently loaded modules access to libslurm symbols. |
| */ |
| extern void libpam_slurm_init (void) |
| { |
| char libslurmname[64]; |
| |
| if (slurm_h) |
| return; |
| |
| /* First try to use the same libslurm version ("libslurm.so.24.0.0"), |
| * Second try to match the major version number ("libslurm.so.24"), |
| * Otherwise use "libslurm.so" */ |
| if (snprintf(libslurmname, sizeof(libslurmname), |
| "libslurm.so.%d.%d.%d", SLURM_API_CURRENT, |
| SLURM_API_REVISION, SLURM_API_AGE) >= |
| sizeof(libslurmname) ) { |
| _log_msg (LOG_ERR, "Unable to write libslurmname\n"); |
| } else if ((slurm_h = dlopen(libslurmname, RTLD_NOW|RTLD_GLOBAL))) { |
| return; |
| } else { |
| _log_msg (LOG_INFO, "Unable to dlopen %s: %s\n", |
| libslurmname, dlerror ()); |
| } |
| |
| if (snprintf(libslurmname, sizeof(libslurmname), "libslurm.so.%d", |
| SLURM_API_CURRENT) >= sizeof(libslurmname) ) { |
| _log_msg (LOG_ERR, "Unable to write libslurmname\n"); |
| } else if ((slurm_h = dlopen(libslurmname, RTLD_NOW|RTLD_GLOBAL))) { |
| return; |
| } else { |
| _log_msg (LOG_INFO, "Unable to dlopen %s: %s\n", |
| libslurmname, dlerror ()); |
| } |
| |
| if (!(slurm_h = dlopen("libslurm.so", RTLD_NOW|RTLD_GLOBAL))) { |
| _log_msg (LOG_ERR, "Unable to dlopen libslurm.so: %s\n", |
| dlerror ()); |
| } |
| |
| return; |
| } |
| |
| extern void libpam_slurm_fini (void) |
| { |
| if (slurm_h) |
| dlclose (slurm_h); |
| return; |
| } |
| |
| |
| /*************************************\ |
| * Statically Loaded Module Struct * |
| \*************************************/ |
| |
| #ifdef PAM_STATIC |
| struct pam_module _pam_rms_modstruct = { |
| "pam_slurm", |
| NULL, |
| NULL, |
| pam_sm_acct_mgmt, |
| NULL, |
| NULL, |
| NULL, |
| }; |
| #endif /* PAM_STATIC */ |