diff options
-rw-r--r-- | modules/pam_limits/pam_limits.8.xml | 27 | ||||
-rw-r--r-- | modules/pam_limits/pam_limits.c | 198 |
2 files changed, 164 insertions, 61 deletions
diff --git a/modules/pam_limits/pam_limits.8.xml b/modules/pam_limits/pam_limits.8.xml index 08c6fc4d..422924fe 100644 --- a/modules/pam_limits/pam_limits.8.xml +++ b/modules/pam_limits/pam_limits.8.xml @@ -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> diff --git a/modules/pam_limits/pam_limits.c b/modules/pam_limits/pam_limits.c index 6fbe95fc..f9489dbe 100644 --- a/modules/pam_limits/pam_limits.c +++ b/modules/pam_limits/pam_limits.c @@ -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; } |