blob: 792acd5b02b8c115cb3de7d0c04cf68940d7a8d1 [file] [log] [blame]
/*****************************************************************************\
* 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 */