963 lines
27 KiB
C
963 lines
27 KiB
C
/* pam_handlers.c -- pam config file parsing and module loading */
|
|
|
|
/*
|
|
* created by Marc Ewing.
|
|
* Currently maintained by Andrew G. Morgan <morgan@kernel.org>
|
|
*
|
|
*/
|
|
|
|
#include "pam_private.h"
|
|
#include "pam_inline.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "pam_line.h"
|
|
|
|
#define MODULE_CHUNK 4
|
|
#define UNKNOWN_MODULE "<*unknown module*>"
|
|
#ifndef _PAM_ISA
|
|
#define _PAM_ISA "."
|
|
#endif
|
|
|
|
static void _pam_free_handlers_aux(struct handler **hp);
|
|
|
|
static int _pam_add_handler(pam_handle_t *pamh
|
|
, int must_fail, int other, int stack_level, int type
|
|
, int *actions, const char *mod_path
|
|
, int argc, char **argv, size_t argvlen);
|
|
|
|
/* Values for module type */
|
|
|
|
#define PAM_T_ANY 0
|
|
#define PAM_T_AUTH 1
|
|
#define PAM_T_SESS 2
|
|
#define PAM_T_ACCT 4
|
|
#define PAM_T_PASS 8
|
|
|
|
static int _pam_load_conf_file(pam_handle_t *pamh, const char *config_name
|
|
, const char *service /* specific file */
|
|
, int module_type /* specific type */
|
|
, int include_level /* level of include */
|
|
, int stack_level /* level of substack */
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, int not_other
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
);
|
|
|
|
static int _pam_parse_conf_file(pam_handle_t *pamh, FILE *f
|
|
, const char *known_service /* specific file */
|
|
, int requested_module_type /* specific type */
|
|
, int include_level /* level of include */
|
|
, int stack_level /* level of substack */
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, int not_other
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
)
|
|
{
|
|
struct pam_line_buffer buffer;
|
|
int x; /* read a line from the FILE *f ? */
|
|
|
|
_pam_line_buffer_init(&buffer);
|
|
/*
|
|
* read a line from the configuration (FILE *) f
|
|
*/
|
|
while ((x = _pam_line_assemble(f, &buffer, ' ')) > 0) {
|
|
char *buf = buffer.assembled;
|
|
char *tok, *nexttok=NULL;
|
|
const char *this_service;
|
|
const char *mod_path;
|
|
int module_type, actions[_PAM_RETURN_VALUES];
|
|
int other; /* set if module is for PAM_DEFAULT_SERVICE */
|
|
int res; /* module added successfully? */
|
|
int handler_type = PAM_HT_MODULE; /* regular handler from a module */
|
|
int argc;
|
|
char **argv;
|
|
size_t argvlen;
|
|
|
|
D(("LINE: %s", buf));
|
|
if (known_service != NULL) {
|
|
nexttok = buf;
|
|
/* No service field: all lines are for the known service. */
|
|
this_service = known_service;
|
|
} else {
|
|
this_service = tok = _pam_tokenize(buf, &nexttok);
|
|
}
|
|
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
if (not_other)
|
|
other = 0;
|
|
else
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
other = !strcasecmp(this_service, PAM_DEFAULT_SERVICE);
|
|
|
|
/* accept "service name" or PAM_DEFAULT_SERVICE modules */
|
|
if (!strcasecmp(this_service, pamh->service_name) || other) {
|
|
int pam_include = 0;
|
|
int substack = 0;
|
|
|
|
/* This is a service we are looking for */
|
|
D(("Found PAM config entry for: %s", this_service));
|
|
|
|
tok = _pam_tokenize(NULL, &nexttok);
|
|
if (tok == NULL) {
|
|
/* module type does not exist */
|
|
D(("empty module type for %s", this_service));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"(%s) empty module type", this_service);
|
|
module_type = (requested_module_type != PAM_T_ANY) ?
|
|
requested_module_type : PAM_T_AUTH; /* most sensitive */
|
|
handler_type = PAM_HT_MUST_FAIL; /* install as normal but fail when dispatched */
|
|
} else {
|
|
if (tok[0] == '-') { /* do not log module load errors */
|
|
handler_type = PAM_HT_SILENT_MODULE;
|
|
++tok;
|
|
}
|
|
if (!strcasecmp("auth", tok)) {
|
|
module_type = PAM_T_AUTH;
|
|
} else if (!strcasecmp("session", tok)) {
|
|
module_type = PAM_T_SESS;
|
|
} else if (!strcasecmp("account", tok)) {
|
|
module_type = PAM_T_ACCT;
|
|
} else if (!strcasecmp("password", tok)) {
|
|
module_type = PAM_T_PASS;
|
|
} else {
|
|
/* Illegal module type */
|
|
D(("bad module type: %s", tok));
|
|
pam_syslog(pamh, LOG_ERR, "(%s) illegal module type: %s",
|
|
this_service, tok);
|
|
module_type = (requested_module_type != PAM_T_ANY) ?
|
|
requested_module_type : PAM_T_AUTH; /* most sensitive */
|
|
handler_type = PAM_HT_MUST_FAIL; /* install as normal but fail when dispatched */
|
|
}
|
|
}
|
|
D(("Using %sconfig entry: %s", handler_type?"BAD ":"", tok));
|
|
if (requested_module_type != PAM_T_ANY &&
|
|
module_type != requested_module_type) {
|
|
D(("Skipping config entry: %s (requested=%d, found=%d)",
|
|
tok, requested_module_type, module_type));
|
|
continue;
|
|
}
|
|
|
|
/* reset the actions to .._UNDEF's -- this is so that
|
|
we can work out which entries are not yet set (for default). */
|
|
{
|
|
int i;
|
|
for (i=0; i<_PAM_RETURN_VALUES;
|
|
actions[i++] = _PAM_ACTION_UNDEF);
|
|
}
|
|
tok = _pam_tokenize(NULL, &nexttok);
|
|
if (tok == NULL) {
|
|
/* no module name given */
|
|
D(("no control flag supplied"));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"(%s) no control flag supplied", this_service);
|
|
_pam_set_default_control(actions, _PAM_ACTION_BAD);
|
|
handler_type = PAM_HT_MUST_FAIL;
|
|
} else if (!strcasecmp("required", tok)) {
|
|
D(("*PAM_F_REQUIRED*"));
|
|
actions[PAM_SUCCESS] = _PAM_ACTION_OK;
|
|
actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_OK;
|
|
actions[PAM_IGNORE] = _PAM_ACTION_IGNORE;
|
|
_pam_set_default_control(actions, _PAM_ACTION_BAD);
|
|
} else if (!strcasecmp("requisite", tok)) {
|
|
D(("*PAM_F_REQUISITE*"));
|
|
actions[PAM_SUCCESS] = _PAM_ACTION_OK;
|
|
actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_OK;
|
|
actions[PAM_IGNORE] = _PAM_ACTION_IGNORE;
|
|
_pam_set_default_control(actions, _PAM_ACTION_DIE);
|
|
} else if (!strcasecmp("optional", tok)) {
|
|
D(("*PAM_F_OPTIONAL*"));
|
|
actions[PAM_SUCCESS] = _PAM_ACTION_OK;
|
|
actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_OK;
|
|
_pam_set_default_control(actions, _PAM_ACTION_IGNORE);
|
|
} else if (!strcasecmp("sufficient", tok)) {
|
|
D(("*PAM_F_SUFFICIENT*"));
|
|
actions[PAM_SUCCESS] = _PAM_ACTION_DONE;
|
|
actions[PAM_NEW_AUTHTOK_REQD] = _PAM_ACTION_DONE;
|
|
_pam_set_default_control(actions, _PAM_ACTION_IGNORE);
|
|
} else if (!strcasecmp("include", tok)) {
|
|
D(("*PAM_F_INCLUDE*"));
|
|
pam_include = 1;
|
|
substack = 0;
|
|
} else if (!strcasecmp("substack", tok)) {
|
|
D(("*PAM_F_SUBSTACK*"));
|
|
pam_include = 1;
|
|
substack = 1;
|
|
} else {
|
|
D(("will need to parse %s", tok));
|
|
_pam_parse_control(actions, tok);
|
|
/* by default the default is to treat as failure */
|
|
_pam_set_default_control(actions, _PAM_ACTION_BAD);
|
|
}
|
|
|
|
tok = _pam_tokenize(NULL, &nexttok);
|
|
if (pam_include) {
|
|
if (substack) {
|
|
res = _pam_add_handler(pamh, PAM_HT_SUBSTACK, other,
|
|
stack_level, module_type, actions, tok,
|
|
0, NULL, 0);
|
|
if (res != PAM_SUCCESS) {
|
|
pam_syslog(pamh, LOG_ERR, "error adding substack %s", tok);
|
|
D(("failed to load module - aborting"));
|
|
return PAM_ABORT;
|
|
}
|
|
}
|
|
if (_pam_load_conf_file(pamh, tok, this_service, module_type,
|
|
include_level + 1, stack_level + substack
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, !other
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
) == PAM_SUCCESS)
|
|
continue;
|
|
_pam_set_default_control(actions, _PAM_ACTION_BAD);
|
|
mod_path = NULL;
|
|
handler_type = PAM_HT_MUST_FAIL;
|
|
nexttok = NULL;
|
|
} else if (tok != NULL) {
|
|
mod_path = tok;
|
|
D(("mod_path = %s",mod_path));
|
|
} else {
|
|
/* no module name given */
|
|
D(("no module name supplied"));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"(%s) no module name supplied", this_service);
|
|
mod_path = NULL;
|
|
handler_type = PAM_HT_MUST_FAIL;
|
|
}
|
|
|
|
/* nexttok points to remaining arguments... */
|
|
|
|
if (nexttok != NULL) {
|
|
D(("list: %s",nexttok));
|
|
argvlen = _pam_mkargv(nexttok, &argv, &argc);
|
|
D(("argvlen = %zu",argvlen));
|
|
if (argvlen == 0) {
|
|
/* memory allocation failed */
|
|
D(("failed to allocate argument vector"));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"(%s) argument vector allocation failed",
|
|
this_service);
|
|
mod_path = NULL;
|
|
handler_type = PAM_HT_MUST_FAIL;
|
|
}
|
|
} else { /* there are no arguments so fix by hand */
|
|
D(("empty argument list"));
|
|
argvlen = 0;
|
|
argc = 0;
|
|
argv = NULL;
|
|
}
|
|
|
|
#ifdef PAM_DEBUG
|
|
{
|
|
int y;
|
|
|
|
D(("CONF%s: %s%s %d %s %d"
|
|
, handler_type==PAM_HT_MUST_FAIL?"<*will fail*>":""
|
|
, this_service, other ? "(backup)":""
|
|
, module_type
|
|
, mod_path, argc));
|
|
for (y = 0; y < argc; y++) {
|
|
D(("CONF: %s", argv[y]));
|
|
}
|
|
for (y = 0; y<_PAM_RETURN_VALUES; ++y) {
|
|
D(("RETURN %s(%d) -> %d %s",
|
|
_pam_token_returns[y], y, actions[y],
|
|
actions[y]>0 ? "jump":
|
|
_pam_token_actions[-actions[y]]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
res = _pam_add_handler(pamh, handler_type, other, stack_level
|
|
, module_type, actions, mod_path
|
|
, argc, argv, argvlen);
|
|
if (res != PAM_SUCCESS) {
|
|
pam_syslog(pamh, LOG_ERR, "error loading %s", mod_path);
|
|
D(("failed to load module - aborting"));
|
|
return PAM_ABORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ( (x < 0) ? PAM_ABORT:PAM_SUCCESS );
|
|
}
|
|
|
|
static int
|
|
_pam_open_config_file(pam_handle_t *pamh
|
|
, const char *service
|
|
, char **path
|
|
, FILE **file)
|
|
{
|
|
const char *const pamd_dirs[] = { PAM_CONFIG_DF, PAM_CONFIG_DIST_DF
|
|
#ifdef VENDORDIR
|
|
, PAM_CONFIG_DIST2_DF
|
|
#endif
|
|
};
|
|
char *p = NULL;
|
|
FILE *f;
|
|
size_t i;
|
|
|
|
/* Absolute path */
|
|
if (service[0] == '/') {
|
|
p = _pam_strdup(service);
|
|
if (p == NULL) {
|
|
pam_syslog(pamh, LOG_CRIT, "strdup failed");
|
|
return PAM_BUF_ERR;
|
|
}
|
|
} else if (pamh->confdir != NULL) {
|
|
if (asprintf (&p, "%s/%s", pamh->confdir, service) < 0) {
|
|
pam_syslog(pamh, LOG_CRIT, "asprintf failed");
|
|
return PAM_BUF_ERR;
|
|
}
|
|
}
|
|
|
|
if (p != NULL) {
|
|
D(("opening %s", p));
|
|
f = fopen(p, "r");
|
|
if (f != NULL) {
|
|
*path = p;
|
|
*file = f;
|
|
return PAM_SUCCESS;
|
|
}
|
|
_pam_drop(p);
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
for (i = 0; i < PAM_ARRAY_SIZE(pamd_dirs); i++) {
|
|
DIAG_PUSH_IGNORE_FORMAT_NONLITERAL
|
|
if (asprintf (&p, pamd_dirs[i], service) < 0) {
|
|
pam_syslog(pamh, LOG_CRIT, "asprintf failed");
|
|
return PAM_BUF_ERR;
|
|
}
|
|
DIAG_POP_IGNORE_FORMAT_NONLITERAL
|
|
|
|
D(("opening %s", p));
|
|
f = fopen(p, "r");
|
|
if (f != NULL) {
|
|
*path = p;
|
|
*file = f;
|
|
return PAM_SUCCESS;
|
|
}
|
|
_pam_drop(p);
|
|
}
|
|
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
static int _pam_load_conf_file(pam_handle_t *pamh, const char *config_name
|
|
, const char *service /* specific file */
|
|
, int module_type /* specific type */
|
|
, int include_level /* level of include */
|
|
, int stack_level /* level of substack */
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, int not_other
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
)
|
|
{
|
|
FILE *f;
|
|
char *path = NULL;
|
|
int retval = PAM_ABORT;
|
|
|
|
D(("called."));
|
|
|
|
if (include_level >= PAM_SUBSTACK_MAX_LEVEL) {
|
|
D(("maximum level of inclusions reached"));
|
|
pam_syslog(pamh, LOG_ERR, "maximum level of inclusions reached");
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
if (config_name == NULL) {
|
|
D(("no config file supplied"));
|
|
pam_syslog(pamh, LOG_ERR, "(%s) no config name supplied", service);
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
if (_pam_open_config_file(pamh, config_name, &path, &f) == PAM_SUCCESS) {
|
|
retval = _pam_parse_conf_file(pamh, f, service, module_type, include_level, stack_level
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, not_other
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
);
|
|
if (retval != PAM_SUCCESS)
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_load_conf_file: error reading %s: %s",
|
|
path, pam_strerror(pamh, retval));
|
|
_pam_drop(path);
|
|
fclose(f);
|
|
} else {
|
|
D(("unable to open %s", config_name));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_load_conf_file: unable to open config for %s",
|
|
config_name);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Parse config file, allocate handler structures, dlopen() */
|
|
int _pam_init_handlers(pam_handle_t *pamh)
|
|
{
|
|
FILE *f;
|
|
int retval;
|
|
|
|
D(("called."));
|
|
IF_NO_PAMH(pamh,PAM_SYSTEM_ERR);
|
|
|
|
/* Return immediately if everything is already loaded */
|
|
if (pamh->handlers.handlers_loaded) {
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
D(("initializing"));
|
|
|
|
/* First clean the service structure */
|
|
|
|
_pam_free_handlers(pamh);
|
|
if (! pamh->handlers.module) {
|
|
if ((pamh->handlers.module =
|
|
malloc(MODULE_CHUNK * sizeof(struct loaded_module))) == NULL) {
|
|
pam_syslog(pamh, LOG_CRIT,
|
|
"_pam_init_handlers: no memory loading module");
|
|
return PAM_BUF_ERR;
|
|
}
|
|
pamh->handlers.modules_allocated = MODULE_CHUNK;
|
|
pamh->handlers.modules_used = 0;
|
|
}
|
|
|
|
if (pamh->service_name == NULL) {
|
|
return PAM_BAD_ITEM; /* XXX - better error? */
|
|
}
|
|
|
|
#ifdef PAM_LOCKING
|
|
/* Is the PAM subsystem locked? */
|
|
{
|
|
int fd_tmp;
|
|
|
|
if ((fd_tmp = open( PAM_LOCK_FILE, O_RDONLY )) != -1) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_init_handlers: PAM lockfile ("
|
|
PAM_LOCK_FILE ") exists - aborting");
|
|
(void) close(fd_tmp);
|
|
/*
|
|
* to avoid swamping the system with requests
|
|
*/
|
|
_pam_start_timer(pamh);
|
|
pam_fail_delay(pamh, 5000000);
|
|
_pam_await_timer(pamh, PAM_ABORT);
|
|
|
|
return PAM_ABORT;
|
|
}
|
|
}
|
|
#endif /* PAM_LOCKING */
|
|
|
|
/*
|
|
* Now parse the config file(s) and add handlers
|
|
*/
|
|
{
|
|
struct stat test_d;
|
|
|
|
/* Is there a PAM_CONFIG_D directory? */
|
|
if (pamh->confdir != NULL ||
|
|
(stat(PAM_CONFIG_D, &test_d) == 0 && S_ISDIR(test_d.st_mode)) ||
|
|
(stat(PAM_CONFIG_DIST_D, &test_d) == 0 && S_ISDIR(test_d.st_mode))
|
|
#ifdef PAM_CONFIG_DIST2_D
|
|
|| (stat(PAM_CONFIG_DIST2_D, &test_d) == 0
|
|
&& S_ISDIR(test_d.st_mode))
|
|
#endif
|
|
) {
|
|
char *path = NULL;
|
|
int read_something=0;
|
|
|
|
if (_pam_open_config_file(pamh, pamh->service_name, &path, &f) == PAM_SUCCESS) {
|
|
retval = _pam_parse_conf_file(pamh, f, pamh->service_name,
|
|
PAM_T_ANY, 0, 0
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, 0
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
);
|
|
if (retval != PAM_SUCCESS) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_init_handlers: error reading %s",
|
|
path);
|
|
pam_syslog(pamh, LOG_ERR, "_pam_init_handlers: [%s]",
|
|
pam_strerror(pamh, retval));
|
|
} else {
|
|
read_something = 1;
|
|
}
|
|
_pam_drop(path);
|
|
fclose(f);
|
|
} else {
|
|
D(("unable to open configuration for %s", pamh->service_name));
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
D(("checking %s", PAM_CONFIG));
|
|
|
|
if (pamh->confdir == NULL
|
|
&& (f = fopen(PAM_CONFIG,"r")) != NULL) {
|
|
retval = _pam_parse_conf_file(pamh, f, NULL, PAM_T_ANY, 0, 0, 1);
|
|
fclose(f);
|
|
} else
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
retval = PAM_SUCCESS;
|
|
/*
|
|
* XXX - should we log an error? Some people want to always
|
|
* use "other"
|
|
*/
|
|
}
|
|
|
|
if (retval == PAM_SUCCESS) {
|
|
/* now parse the PAM_DEFAULT_SERVICE */
|
|
|
|
if (_pam_open_config_file(pamh, PAM_DEFAULT_SERVICE, &path, &f) == PAM_SUCCESS) {
|
|
/* would test magic here? */
|
|
retval = _pam_parse_conf_file(pamh, f, PAM_DEFAULT_SERVICE,
|
|
PAM_T_ANY, 0, 0
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, 0
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
);
|
|
if (retval != PAM_SUCCESS) {
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_init_handlers: error reading %s",
|
|
path);
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_init_handlers: [%s]",
|
|
pam_strerror(pamh, retval));
|
|
} else {
|
|
read_something = 1;
|
|
}
|
|
_pam_drop(path);
|
|
fclose(f);
|
|
} else {
|
|
D(("unable to open configuration for %s", PAM_DEFAULT_SERVICE));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"_pam_init_handlers: no default config %s",
|
|
PAM_DEFAULT_SERVICE);
|
|
}
|
|
if (!read_something) { /* nothing read successfully */
|
|
retval = PAM_ABORT;
|
|
}
|
|
}
|
|
} else {
|
|
if ((f = fopen(PAM_CONFIG, "r")) == NULL) {
|
|
pam_syslog(pamh, LOG_ERR, "_pam_init_handlers: could not open "
|
|
PAM_CONFIG );
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
retval = _pam_parse_conf_file(pamh, f, NULL, PAM_T_ANY, 0, 0
|
|
#ifdef PAM_READ_BOTH_CONFS
|
|
, 0
|
|
#endif /* PAM_READ_BOTH_CONFS */
|
|
);
|
|
|
|
D(("closing configuration file"));
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
if (retval != PAM_SUCCESS) {
|
|
/* Read error */
|
|
pam_syslog(pamh, LOG_ERR, "error reading PAM configuration file");
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
pamh->handlers.handlers_loaded = 1;
|
|
|
|
D(("exiting"));
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
static char *
|
|
extract_modulename(const char *mod_path)
|
|
{
|
|
const char *p = strrchr (mod_path, '/');
|
|
char *dot, *retval;
|
|
|
|
if (p == NULL)
|
|
p = mod_path;
|
|
else
|
|
p++;
|
|
|
|
if ((retval = _pam_strdup (p)) == NULL)
|
|
return NULL;
|
|
|
|
dot = strrchr (retval, '.');
|
|
if (dot)
|
|
*dot = '\0';
|
|
|
|
if (*retval == '\0' || strcmp(retval, "?") == 0) {
|
|
/* do not allow empty module name or "?" to avoid confusing audit trail */
|
|
_pam_drop(retval);
|
|
return NULL;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static struct loaded_module *
|
|
_pam_load_module(pam_handle_t *pamh, const char *mod_path, int handler_type)
|
|
{
|
|
int x = 0;
|
|
int success;
|
|
struct loaded_module *mod;
|
|
|
|
D(("loading module `%s'", mod_path));
|
|
|
|
mod = pamh->handlers.module;
|
|
|
|
/* First, ensure the module is loaded */
|
|
while (x < pamh->handlers.modules_used) {
|
|
if (!strcmp(mod[x].name, mod_path)) { /* case sensitive ! */
|
|
break;
|
|
}
|
|
x++;
|
|
}
|
|
if (x == pamh->handlers.modules_used) {
|
|
/* Not found */
|
|
if (pamh->handlers.modules_allocated == pamh->handlers.modules_used) {
|
|
/* will need more memory */
|
|
void *tmp = realloc(pamh->handlers.module,
|
|
(pamh->handlers.modules_allocated+MODULE_CHUNK)
|
|
*sizeof(struct loaded_module));
|
|
if (tmp == NULL) {
|
|
D(("cannot enlarge module pointer memory"));
|
|
pam_syslog(pamh, LOG_CRIT,
|
|
"realloc returned NULL in _pam_load_module");
|
|
return NULL;
|
|
}
|
|
pamh->handlers.module = tmp;
|
|
pamh->handlers.modules_allocated += MODULE_CHUNK;
|
|
}
|
|
mod = &(pamh->handlers.module[x]);
|
|
/* Be pessimistic... */
|
|
success = PAM_ABORT;
|
|
|
|
D(("_pam_dlopen(%s)", mod_path));
|
|
mod->dl_handle = _pam_dlopen(mod_path);
|
|
D(("_pam_dlopen'ed"));
|
|
D(("dlopen'ed"));
|
|
if (mod->dl_handle == NULL) {
|
|
const char *isa = strstr(mod_path, "$ISA");
|
|
size_t isa_len = strlen("$ISA");
|
|
|
|
if (isa != NULL) {
|
|
char *mod_full_isa_path = NULL;
|
|
if (strlen(mod_path) >= INT_MAX ||
|
|
asprintf(&mod_full_isa_path, "%.*s%s%s",
|
|
(int)(isa - mod_path), mod_path, _PAM_ISA, isa + isa_len) < 0) {
|
|
D(("couldn't get memory for mod_path"));
|
|
pam_syslog(pamh, LOG_CRIT, "no memory for module path");
|
|
success = PAM_ABORT;
|
|
} else {
|
|
mod->dl_handle = _pam_dlopen(mod_full_isa_path);
|
|
_pam_drop(mod_full_isa_path);
|
|
}
|
|
}
|
|
}
|
|
if (mod->dl_handle == NULL) {
|
|
D(("_pam_dlopen(%s) failed", mod_path));
|
|
if (handler_type != PAM_HT_SILENT_MODULE)
|
|
pam_syslog(pamh, LOG_ERR, "unable to dlopen(%s): %s", mod_path,
|
|
_pam_dlerror());
|
|
/* Don't abort yet; static code may be able to find function.
|
|
* But defaults to abort if nothing found below... */
|
|
} else {
|
|
D(("module added successfully"));
|
|
success = PAM_SUCCESS;
|
|
mod->type = PAM_MT_DYNAMIC_MOD;
|
|
pamh->handlers.modules_used++;
|
|
}
|
|
|
|
if (success != PAM_SUCCESS) { /* add a malformed module */
|
|
mod->dl_handle = NULL;
|
|
mod->type = PAM_MT_FAULTY_MOD;
|
|
pamh->handlers.modules_used++;
|
|
if (handler_type != PAM_HT_SILENT_MODULE)
|
|
pam_syslog(pamh, LOG_ERR, "adding faulty module: %s", mod_path);
|
|
success = PAM_SUCCESS; /* We have successfully added a module */
|
|
}
|
|
|
|
/* indicate its name - later we will search for it by this */
|
|
if ((mod->name = _pam_strdup(mod_path)) == NULL) {
|
|
D(("couldn't get memory for mod_path"));
|
|
pam_syslog(pamh, LOG_CRIT, "no memory for module path");
|
|
success = PAM_ABORT;
|
|
}
|
|
|
|
} else { /* x != pamh->handlers.modules_used */
|
|
mod += x; /* the located module */
|
|
success = PAM_SUCCESS;
|
|
}
|
|
return success == PAM_SUCCESS ? mod : NULL;
|
|
}
|
|
|
|
static int _pam_add_handler(pam_handle_t *pamh
|
|
, int handler_type, int other, int stack_level
|
|
, int type, int *actions, const char *mod_path
|
|
, int argc, char **argv, size_t argvlen)
|
|
{
|
|
struct loaded_module *mod = NULL;
|
|
struct handler **handler_p;
|
|
struct handler **handler_p2;
|
|
struct handlers *the_handlers;
|
|
const char *sym, *sym2;
|
|
char *mod_full_path;
|
|
servicefn func, func2;
|
|
int mod_type = PAM_MT_FAULTY_MOD;
|
|
|
|
D(("called."));
|
|
IF_NO_PAMH(pamh,PAM_SYSTEM_ERR);
|
|
|
|
D(("adding type %d, handler_type %d, module `%s'",
|
|
type, handler_type, mod_path));
|
|
|
|
if ((handler_type == PAM_HT_MODULE || handler_type == PAM_HT_SILENT_MODULE) &&
|
|
mod_path != NULL) {
|
|
if (mod_path[0] == '/') {
|
|
mod = _pam_load_module(pamh, mod_path, handler_type);
|
|
} else if (asprintf(&mod_full_path, "%s%s",
|
|
DEFAULT_MODULE_PATH, mod_path) >= 0) {
|
|
mod = _pam_load_module(pamh, mod_full_path, handler_type);
|
|
_pam_drop(mod_full_path);
|
|
} else {
|
|
pam_syslog(pamh, LOG_CRIT, "cannot malloc full mod path");
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
if (mod == NULL) {
|
|
/* if we get here with NULL it means allocation error */
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
mod_type = mod->type;
|
|
}
|
|
|
|
if (mod_path == NULL)
|
|
mod_path = UNKNOWN_MODULE;
|
|
|
|
/*
|
|
* At this point 'mod' points to the stored/loaded module.
|
|
*/
|
|
|
|
/* Now define the handler(s) based on mod->dlhandle and type */
|
|
|
|
/* decide which list of handlers to use */
|
|
the_handlers = (other) ? &pamh->handlers.other : &pamh->handlers.conf;
|
|
|
|
handler_p = handler_p2 = NULL;
|
|
func = func2 = NULL;
|
|
sym2 = NULL;
|
|
|
|
/* point handler_p's at the root addresses of the function stacks */
|
|
switch (type) {
|
|
case PAM_T_AUTH:
|
|
handler_p = &the_handlers->authenticate;
|
|
sym = "pam_sm_authenticate";
|
|
handler_p2 = &the_handlers->setcred;
|
|
sym2 = "pam_sm_setcred";
|
|
break;
|
|
case PAM_T_SESS:
|
|
handler_p = &the_handlers->open_session;
|
|
sym = "pam_sm_open_session";
|
|
handler_p2 = &the_handlers->close_session;
|
|
sym2 = "pam_sm_close_session";
|
|
break;
|
|
case PAM_T_ACCT:
|
|
handler_p = &the_handlers->acct_mgmt;
|
|
sym = "pam_sm_acct_mgmt";
|
|
break;
|
|
case PAM_T_PASS:
|
|
handler_p = &the_handlers->chauthtok;
|
|
sym = "pam_sm_chauthtok";
|
|
break;
|
|
default:
|
|
/* Illegal module type */
|
|
D(("illegal module type %d", type));
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
/* are the modules reliable? */
|
|
if (mod_type != PAM_MT_DYNAMIC_MOD &&
|
|
mod_type != PAM_MT_FAULTY_MOD) {
|
|
D(("illegal module library type; %d", mod_type));
|
|
pam_syslog(pamh, LOG_ERR,
|
|
"internal error: module library type not known: %s;%d",
|
|
sym, mod_type);
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
/* now identify this module's functions - for non-faulty modules */
|
|
|
|
if ((mod_type == PAM_MT_DYNAMIC_MOD) &&
|
|
!(func = _pam_dlsym(mod->dl_handle, sym)) ) {
|
|
pam_syslog(pamh, LOG_ERR, "unable to resolve symbol: %s", sym);
|
|
}
|
|
if (sym2) {
|
|
if ((mod_type == PAM_MT_DYNAMIC_MOD) &&
|
|
!(func2 = _pam_dlsym(mod->dl_handle, sym2)) ) {
|
|
pam_syslog(pamh, LOG_ERR, "unable to resolve symbol: %s", sym2);
|
|
}
|
|
}
|
|
|
|
/* here func (and perhaps func2) point to the appropriate functions */
|
|
|
|
/* add new handler to end of existing list */
|
|
while (*handler_p != NULL) {
|
|
handler_p = &((*handler_p)->next);
|
|
}
|
|
|
|
if ((*handler_p = calloc(1, sizeof(struct handler))) == NULL) {
|
|
pam_syslog(pamh, LOG_CRIT, "cannot allocate struct handler #1");
|
|
return (PAM_ABORT);
|
|
}
|
|
|
|
(*handler_p)->handler_type = handler_type;
|
|
(*handler_p)->stack_level = stack_level;
|
|
(*handler_p)->func = func;
|
|
memcpy((*handler_p)->actions,actions,sizeof((*handler_p)->actions));
|
|
(*handler_p)->cached_retval = _PAM_INVALID_RETVAL;
|
|
(*handler_p)->cached_retval_p = &((*handler_p)->cached_retval);
|
|
(*handler_p)->argc = argc;
|
|
(*handler_p)->argv = argv; /* not a copy */
|
|
if (((*handler_p)->mod_name = extract_modulename(mod_path)) == NULL)
|
|
return PAM_ABORT;
|
|
|
|
/* some of the modules have a second calling function */
|
|
if (handler_p2) {
|
|
/* add new handler to end of existing list */
|
|
while (*handler_p2) {
|
|
handler_p2 = &((*handler_p2)->next);
|
|
}
|
|
|
|
if ((*handler_p2 = calloc(1, sizeof(struct handler))) == NULL) {
|
|
pam_syslog(pamh, LOG_CRIT, "cannot allocate struct handler #2");
|
|
return (PAM_ABORT);
|
|
}
|
|
|
|
(*handler_p2)->handler_type = handler_type;
|
|
(*handler_p2)->stack_level = stack_level;
|
|
(*handler_p2)->func = func2;
|
|
memcpy((*handler_p2)->actions,actions,sizeof((*handler_p2)->actions));
|
|
(*handler_p2)->cached_retval = _PAM_INVALID_RETVAL; /* ignored */
|
|
/* Note, this next entry points to the handler_p value! */
|
|
(*handler_p2)->cached_retval_p = &((*handler_p)->cached_retval);
|
|
(*handler_p2)->argc = argc;
|
|
if (argv) {
|
|
if (((*handler_p2)->argv = malloc(argvlen)) == NULL) {
|
|
pam_syslog(pamh, LOG_CRIT, "cannot malloc argv for handler #2");
|
|
return (PAM_ABORT);
|
|
}
|
|
memcpy((*handler_p2)->argv, argv, argvlen);
|
|
}
|
|
if (((*handler_p2)->mod_name = extract_modulename(mod_path)) == NULL)
|
|
return PAM_ABORT;
|
|
}
|
|
|
|
D(("returning successfully"));
|
|
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
/* Free various allocated structures and dlclose() the libs */
|
|
int _pam_free_handlers(pam_handle_t *pamh)
|
|
{
|
|
struct loaded_module *mod;
|
|
|
|
D(("called."));
|
|
IF_NO_PAMH(pamh,PAM_SYSTEM_ERR);
|
|
|
|
mod = pamh->handlers.module;
|
|
|
|
/* Close all loaded modules */
|
|
|
|
while (pamh->handlers.modules_used) {
|
|
D(("dlclose(%s)", mod->name));
|
|
free(mod->name);
|
|
if (mod->type == PAM_MT_DYNAMIC_MOD) {
|
|
_pam_dlclose(mod->dl_handle);
|
|
}
|
|
mod++;
|
|
pamh->handlers.modules_used--;
|
|
}
|
|
|
|
/* Free all the handlers */
|
|
|
|
_pam_free_handlers_aux(&(pamh->handlers.conf.authenticate));
|
|
_pam_free_handlers_aux(&(pamh->handlers.conf.setcred));
|
|
_pam_free_handlers_aux(&(pamh->handlers.conf.acct_mgmt));
|
|
_pam_free_handlers_aux(&(pamh->handlers.conf.open_session));
|
|
_pam_free_handlers_aux(&(pamh->handlers.conf.close_session));
|
|
_pam_free_handlers_aux(&(pamh->handlers.conf.chauthtok));
|
|
|
|
_pam_free_handlers_aux(&(pamh->handlers.other.authenticate));
|
|
_pam_free_handlers_aux(&(pamh->handlers.other.setcred));
|
|
_pam_free_handlers_aux(&(pamh->handlers.other.acct_mgmt));
|
|
_pam_free_handlers_aux(&(pamh->handlers.other.open_session));
|
|
_pam_free_handlers_aux(&(pamh->handlers.other.close_session));
|
|
_pam_free_handlers_aux(&(pamh->handlers.other.chauthtok));
|
|
|
|
/* no more loaded modules */
|
|
|
|
_pam_drop(pamh->handlers.module);
|
|
|
|
/* Indicate that handlers are not initialized for this pamh */
|
|
|
|
pamh->handlers.handlers_loaded = 0;
|
|
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
void _pam_start_handlers(pam_handle_t *pamh)
|
|
{
|
|
D(("called."));
|
|
/* NB. There is no check for a NULL pamh here, since no return
|
|
* value to communicate the fact! */
|
|
|
|
/* Indicate that handlers are not initialized for this pamh */
|
|
pamh->handlers.handlers_loaded = 0;
|
|
|
|
pamh->handlers.modules_allocated = 0;
|
|
pamh->handlers.modules_used = 0;
|
|
pamh->handlers.module = NULL;
|
|
|
|
/* initialize the .conf and .other entries */
|
|
|
|
pamh->handlers.conf.authenticate = NULL;
|
|
pamh->handlers.conf.setcred = NULL;
|
|
pamh->handlers.conf.acct_mgmt = NULL;
|
|
pamh->handlers.conf.open_session = NULL;
|
|
pamh->handlers.conf.close_session = NULL;
|
|
pamh->handlers.conf.chauthtok = NULL;
|
|
|
|
pamh->handlers.other.authenticate = NULL;
|
|
pamh->handlers.other.setcred = NULL;
|
|
pamh->handlers.other.acct_mgmt = NULL;
|
|
pamh->handlers.other.open_session = NULL;
|
|
pamh->handlers.other.close_session = NULL;
|
|
pamh->handlers.other.chauthtok = NULL;
|
|
}
|
|
|
|
void _pam_free_handlers_aux(struct handler **hp)
|
|
{
|
|
struct handler *h = *hp;
|
|
struct handler *last;
|
|
|
|
D(("called."));
|
|
while (h) {
|
|
last = h;
|
|
_pam_drop(h->argv); /* This is all allocated in a single chunk */
|
|
_pam_drop(h->mod_name);
|
|
h = h->next;
|
|
pam_overwrite_object(last);
|
|
free(last);
|
|
}
|
|
|
|
*hp = NULL;
|
|
}
|