pam_limits: use vendor specific content in limits.d directory as fallback

Use the vendor directory as fallback for a distribution provided default
config if there is no configuration in /etc.

pam_limits.c: Take care about the fallback configuration in vendor directory.
pam_limits.8.xml: Add description for vendor directory.
This commit is contained in:
Stefan Schubert 2022-02-07 23:31:13 +01:00 committed by Dmitry V. Levin
parent 8f9816b57e
commit 21affb5b1b
2 changed files with 164 additions and 61 deletions

View File

@ -48,7 +48,7 @@
obtained in a user-session. Users of <emphasis>uid=0</emphasis> are affected
by this limits, too.
</para>
<para>
<para condition="without_vendordir">
By default limits are taken from the <filename>/etc/security/limits.conf</filename>
config file. Then individual *.conf files from the <filename>/etc/security/limits.d/</filename>
directory are read. The files are parsed one after another in the order of "C" locale.
@ -58,9 +58,21 @@
files in the above directory are not parsed.
</para>
<para condition="with_vendordir">
If there is no explicitly specified configuration file and
<filename>/etc/security/limits.conf</filename> does not exist,
<filename>%vendordir%/security/limits.conf</filename> is used.
By default limits are taken from the <filename>/etc/security/limits.conf</filename>
config file or, if that one is not present, the file
<filename>%vendordir%/security/limits.conf</filename>.
Then individual <filename>*.conf</filename> files from the
<filename>/etc/security/limits.d/</filename> and
<filename>%vendordir%/security/limits.d</filename> directories are read.
If <filename>/etc/security/limits.d/@filename@.conf</filename> exists, then
<filename>%vendordir%/security/limits.d/@filename@.conf</filename> will not be used.
All <filename>limits.d/*.conf</filename> files are sorted by their
<filename>@filename@.conf</filename> in lexicographic order regardless of which
of the directories they reside in.
The effect of the individual files is the same as if all the files were
concatenated together in the order of parsing.
If a config file is explicitly specified with the <option>config</option>
option the files in the above directories are not parsed.
</para>
<para>
The module must not be called by a multithreaded application.
@ -216,6 +228,13 @@
<para>Default configuration file</para>
</listitem>
</varlistentry>
<varlistentry condition="with_vendordir">
<term><filename>%vendordir%/security/limits.conf</filename></term>
<listitem>
<para>Default configuration file if
<filename>/etc/security/limits.conf</filename> does not exist.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -126,7 +126,11 @@ struct pam_limit_s {
#define LIMITS_CONF_GLOB (LIMITS_FILE_DIR "/*.conf")
#define LIMITS_FILE (SCONFIGDIR "/limits.conf")
#define CONF_FILE ((pl->conf_file != NULL) ? pl->conf_file : LIMITS_FILE)
#ifdef VENDOR_SCONFIGDIR
#define VENDOR_LIMITS_FILE (VENDOR_SCONFIGDIR "/limits.conf")
#define VENDOR_LIMITS_CONF_GLOB (VENDOR_SCONFIGDIR "/limits.d/*.conf")
#endif
static int
_pam_parse (const pam_handle_t *pamh, int argc, const char **argv,
@ -811,35 +815,23 @@ parse_uid_range(pam_handle_t *pamh, const char *domain,
static int
parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
int ctrl, struct pam_limit_s *pl)
int ctrl, struct pam_limit_s *pl, const int conf_file_set_by_user)
{
FILE *fil;
char buf[LINE_LENGTH];
/* check for the CONF_FILE */
/* check for the conf_file */
if (ctrl & PAM_DEBUG_ARG)
pam_syslog(pamh, LOG_DEBUG, "reading settings from '%s'", CONF_FILE);
fil = fopen(CONF_FILE, "r");
pam_syslog(pamh, LOG_DEBUG, "reading settings from '%s'", pl->conf_file);
fil = fopen(pl->conf_file, "r");
if (fil == NULL) {
int err = errno;
if (errno == ENOENT && !conf_file_set_by_user)
return PAM_SUCCESS; /* file is not there and it has not been set by the conf= argument */
#ifdef VENDOR_SCONFIGDIR
/* if the specified file does not exist, and it is not provided by
the user, try the vendor file as fallback. */
if (pl->conf_file == NULL && err == ENOENT)
fil = fopen(VENDOR_SCONFIGDIR "/limits.conf", "r");
if (fil == NULL)
#endif
{
if (err == ENOENT)
return PAM_SUCCESS;
pam_syslog (pamh, LOG_WARNING,
"cannot read settings from %s: %s", CONF_FILE,
strerror(err));
return PAM_SERVICE_ERR;
}
pam_syslog(pamh, LOG_WARNING,
"cannot read settings from %s: %s", pl->conf_file,
strerror(errno));
return PAM_SERVICE_ERR;
}
/* start the show */
@ -1095,33 +1087,132 @@ static int setup_limits(pam_handle_t *pamh,
return retval;
}
/* --- evaluting all files in VENDORDIR/security/limits.d and /etc/security/limits.d --- */
static const char *
base_name(const char *path)
{
const char *base = strrchr(path, '/');
return base ? base+1 : path;
}
static int
compare_filename(const void *a, const void *b)
{
return strcmp(base_name(* (const char * const *) a),
base_name(* (const char * const *) b));
}
/* Evaluating a list of files which have to be parsed in the right order:
*
* - If etc/security/limits.d/@filename@.conf exists, then
* %vendordir%/security/limits.d/@filename@.conf should not be used.
* - All files in both limits.d directories are sorted by their @filename@.conf in
* lexicographic order regardless of which of the directories they reside in. */
static char **
read_limits_dir(pam_handle_t *pamh)
{
glob_t globbuf;
size_t i=0;
int glob_rv = glob(LIMITS_CONF_GLOB, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf);
char **file_list;
size_t file_list_size = glob_rv == 0 ? globbuf.gl_pathc : 0;
#ifdef VENDOR_LIMITS_CONF_GLOB
glob_t globbuf_vendor;
int glob_rv_vendor = glob(VENDOR_LIMITS_CONF_GLOB, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf_vendor);
if (glob_rv_vendor == 0)
file_list_size += globbuf_vendor.gl_pathc;
#endif
file_list = malloc((file_list_size + 1) * sizeof(char*));
if (file_list == NULL) {
pam_syslog(pamh, LOG_ERR, "Cannot allocate memory for file list: %m");
#ifdef VENDOR_ACCESS_CONF_GLOB
if (glob_rv_vendor == 0)
globfree(&globbuf_vendor);
#endif
if (glob_rv == 0)
globfree(&globbuf);
return NULL;
}
if (glob_rv == 0) {
for (i = 0; i < globbuf.gl_pathc; i++) {
file_list[i] = strdup(globbuf.gl_pathv[i]);
if (file_list[i] == NULL) {
pam_syslog(pamh, LOG_ERR, "strdup failed: %m");
break;
}
}
}
#ifdef VENDOR_LIMITS_CONF_GLOB
if (glob_rv_vendor == 0) {
for (size_t j = 0; j < globbuf_vendor.gl_pathc; j++) {
if (glob_rv == 0 && globbuf.gl_pathc > 0) {
int double_found = 0;
for (size_t k = 0; k < globbuf.gl_pathc; k++) {
if (strcmp(base_name(globbuf.gl_pathv[k]),
base_name(globbuf_vendor.gl_pathv[j])) == 0) {
double_found = 1;
break;
}
}
if (double_found)
continue;
}
file_list[i] = strdup(globbuf_vendor.gl_pathv[j]);
if (file_list[i] == NULL) {
pam_syslog(pamh, LOG_ERR, "strdup failed: %m");
break;
}
i++;
}
globfree(&globbuf_vendor);
}
#endif
file_list[i] = NULL;
qsort(file_list, i, sizeof(char *), compare_filename);
if (glob_rv == 0)
globfree(&globbuf);
return file_list;
}
/* now the session stuff */
int
pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
int argc, const char **argv)
{
int retval;
int i;
int glob_rc;
int retval, i;
char *user_name;
struct passwd *pwd;
int ctrl;
struct pam_limit_s plstruct;
struct pam_limit_s *pl = &plstruct;
glob_t globbuf;
const char *oldlocale;
D(("called."));
memset(pl, 0, sizeof(*pl));
memset(&globbuf, 0, sizeof(globbuf));
ctrl = _pam_parse(pamh, argc, argv, pl);
retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
if ( user_name == NULL || retval != PAM_SUCCESS ) {
pam_syslog(pamh, LOG_ERR, "open_session - error recovering username");
return PAM_SESSION_ERR;
}
}
int conf_file_set_by_user = (pl->conf_file != NULL);
if (pl->conf_file == NULL) {
pl->conf_file = LIMITS_FILE;
#ifdef VENDOR_LIMITS_FILE
/*
* Check whether LIMITS_FILE file is available.
* If it does not exist, fall back to VENDOR_LIMITS_FILE file.
*/
struct stat buffer;
if (stat(pl->conf_file, &buffer) != 0 && errno == ENOENT)
pl->conf_file = VENDOR_LIMITS_FILE;
#endif
}
pwd = pam_modutil_getpwnam(pamh, user_name);
if (!pwd) {
@ -1137,46 +1228,39 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
return PAM_ABORT;
}
retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);
retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid,
ctrl, pl, conf_file_set_by_user);
if (retval == PAM_IGNORE) {
D(("the configuration file ('%s') has an applicable '<domain> -' entry", CONF_FILE));
D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
return PAM_SUCCESS;
}
if (retval != PAM_SUCCESS || pl->conf_file != NULL)
if (retval != PAM_SUCCESS || conf_file_set_by_user)
/* skip reading limits.d if config file explicitly specified */
goto out;
/* Read subsequent *.conf files, if they exist. */
/* set the LC_COLLATE so the sorting order doesn't depend
on system locale */
oldlocale = setlocale(LC_COLLATE, "C");
glob_rc = glob(LIMITS_CONF_GLOB, GLOB_ERR, NULL, &globbuf);
if (oldlocale != NULL)
setlocale (LC_COLLATE, oldlocale);
if (!glob_rc) {
/* Parse the *.conf files. */
for (i = 0; globbuf.gl_pathv[i] != NULL; i++) {
pl->conf_file = globbuf.gl_pathv[i];
retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);
if (retval == PAM_IGNORE) {
D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
globfree(&globbuf);
return PAM_SUCCESS;
}
if (retval != PAM_SUCCESS)
goto out;
char **filename_list = read_limits_dir(pamh);
if (filename_list != NULL) {
for (i = 0; filename_list[i] != NULL; i++) {
pl->conf_file = filename_list[i];
retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl, 0);
if (retval != PAM_SUCCESS)
break;
}
for (i = 0; filename_list[i] != NULL; i++)
free(filename_list[i]);
free(filename_list);
}
if (retval == PAM_IGNORE) {
D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
return PAM_SUCCESS;
}
out:
globfree(&globbuf);
if (retval != PAM_SUCCESS)
{
pam_syslog(pamh, LOG_ERR, "error parsing the configuration file: '%s' ",CONF_FILE);
pam_syslog(pamh, LOG_ERR, "error parsing the configuration file: '%s' ", pl->conf_file);
return retval;
}