aboutsummaryrefslogtreecommitdiff
path: root/modules/pam_limits/pam_limits.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/pam_limits/pam_limits.c')
-rw-r--r--modules/pam_limits/pam_limits.c249
1 files changed, 202 insertions, 47 deletions
diff --git a/modules/pam_limits/pam_limits.c b/modules/pam_limits/pam_limits.c
index 87bb4b70..1e4dfa3d 100644
--- a/modules/pam_limits/pam_limits.c
+++ b/modules/pam_limits/pam_limits.c
@@ -28,13 +28,20 @@
#include <syslog.h>
#include <stdarg.h>
#include <signal.h>
+#ifdef __linux__
#include <sys/prctl.h>
+#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <limits.h>
#include <glob.h>
+#ifdef USE_LOGIND
+#include <systemd/sd-login.h>
+#else
#include <utmp.h>
+#endif
+
#ifndef UT_USER /* some systems have ut_name instead of ut_user */
#define UT_USER ut_user
#endif
@@ -52,8 +59,6 @@
#endif
/* Module defines */
-#define LINE_LENGTH 1024
-
#define LIMITS_DEF_USER 0 /* limit was set by a user entry */
#define LIMITS_DEF_GROUP 1 /* limit was set by a group entry */
#define LIMITS_DEF_ALLGROUP 2 /* limit was set by a group entry */
@@ -62,13 +67,13 @@
#define LIMITS_DEF_KERNEL 5 /* limit was set from /proc/1/limits */
#define LIMITS_DEF_NONE 6 /* this limit was not set yet */
-#define LIMIT_RANGE_ERR -1 /* error in specified uid/gid range */
+#define LIMIT_RANGE_ERR (-1) /* error in specified uid/gid range */
#define LIMIT_RANGE_NONE 0 /* no range specified */
#define LIMIT_RANGE_ONE 1 /* exact uid/gid specified (:max_uid)*/
#define LIMIT_RANGE_MIN 2 /* only minimum uid/gid specified (min_uid:) */
#define LIMIT_RANGE_MM 3 /* both min and max uid/gid specified (min_uid:max_uid) */
-static const char *limits_def_names[] = {
+static const char *const limits_def_names[] = {
"USER",
"GROUP",
"ALLGROUP",
@@ -97,14 +102,14 @@ struct pam_limit_s {
struct user_limits_struct limits[RLIM_NLIMITS];
const char *conf_file;
int utmp_after_pam_call;
- char login_group[LINE_LENGTH];
+ char *login_group;
};
-#define LIMIT_LOGIN RLIM_NLIMITS+1
-#define LIMIT_NUMSYSLOGINS RLIM_NLIMITS+2
+#define LIMIT_LOGIN (RLIM_NLIMITS+1)
+#define LIMIT_NUMSYSLOGINS (RLIM_NLIMITS+2)
-#define LIMIT_PRI RLIM_NLIMITS+3
-#define LIMIT_NONEWPRIVS RLIM_NLIMITS+4
+#define LIMIT_PRI (RLIM_NLIMITS+3)
+#define LIMIT_NONEWPRIVS (RLIM_NLIMITS+4)
#define LIMIT_SOFT 1
#define LIMIT_HARD 2
@@ -114,6 +119,7 @@ struct pam_limit_s {
#include <security/pam_modutil.h>
#include <security/pam_ext.h>
#include "pam_inline.h"
+#include "pam_i18n.h"
/* argument parsing */
@@ -125,11 +131,11 @@ struct pam_limit_s {
/* Limits from globbed files. */
#define LIMITS_CONF_GLOB (LIMITS_FILE_DIR "/*.conf")
-#define LIMITS_FILE (SCONFIGDIR "/limits.conf")
+#define LIMITS_FILE (SCONFIG_DIR "/limits.conf")
-#ifdef VENDOR_SCONFIGDIR
-#define VENDOR_LIMITS_FILE (VENDOR_SCONFIGDIR "/limits.conf")
-#define VENDOR_LIMITS_CONF_GLOB (VENDOR_SCONFIGDIR "/limits.d/*.conf")
+#ifdef VENDOR_SCONFIG_DIR
+#define VENDOR_LIMITS_FILE (VENDOR_SCONFIG_DIR "/limits.conf")
+#define VENDOR_LIMITS_CONF_GLOB (VENDOR_SCONFIG_DIR "/limits.d/*.conf")
#endif
static int
@@ -239,7 +245,6 @@ static int
check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
struct pam_limit_s *pl)
{
- struct utmp *ut;
int count;
if (ctrl & PAM_DEBUG_ARG) {
@@ -254,8 +259,6 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
return LOGIN_ERR;
}
- setutent();
-
/* Because there is no definition about when an application
actually adds a utmp entry, some applications bizarrely do the
utmp call before the have PAM authenticate them to the system:
@@ -272,6 +275,78 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
count = 1;
}
+#ifdef USE_LOGIND
+ char **sessions_list;
+ int sessions = sd_get_sessions(&sessions_list);
+
+ /* maxlogins needs to be 2 with systemd-logind because
+ of the systemd --user process started with first login by
+ pam_systemd.
+ Which is also calling pam_limits, but in this very first special
+ case the session does already exist and is counted twice.
+ With start of the second session, session manager is already running
+ and no longer counted. */
+ if (limit == 1) {
+ pam_syslog(pamh, LOG_WARNING, "Maxlogin limit needs to be 2 or higher with systemd-logind");
+ return LIMIT_ERR;
+ }
+
+ if (sessions < 0) {
+ pam_syslog(pamh, LOG_ERR, "logind error getting session list: %s",
+ strerror(-sessions));
+ return LIMIT_ERR;
+ } else if (sessions > 0 && sessions_list != NULL && !pl->flag_numsyslogins) {
+ int i;
+
+ for (i = 0; i < sessions; i++) {
+ char *user = NULL;
+ char *class = NULL;
+
+ if (sd_session_get_class(sessions_list[i], &class) < 0 || class == NULL)
+ continue;
+
+ if (strncmp(class, "user", 4) != 0) { /* user, user-early, user-incomplete */
+ free (class);
+ continue;
+ }
+ free (class);
+
+ if (sd_session_get_username(sessions_list[i], &user) < 0 || user == NULL) {
+ pam_syslog(pamh, LOG_ERR, "logind error getting username: %s",
+ strerror(-sessions));
+ return LIMIT_ERR;
+ }
+
+ if (((pl->login_limit_def == LIMITS_DEF_USER)
+ || (pl->login_limit_def == LIMITS_DEF_GROUP)
+ || (pl->login_limit_def == LIMITS_DEF_DEFAULT))
+ && strcmp(name, user) != 0) {
+ free(user);
+ continue;
+ }
+ if ((pl->login_limit_def == LIMITS_DEF_ALLGROUP)
+ && pl->login_group != NULL
+ && !pam_modutil_user_in_group_nam_nam(pamh, user, pl->login_group)) {
+ free(user);
+ continue;
+ }
+ free(user);
+
+ if (++count > limit) {
+ break;
+ }
+ }
+ for (i = 0; i < sessions; i++)
+ free(sessions_list[i]);
+ free(sessions_list);
+ } else {
+ count = sessions;
+ }
+#else
+ struct utmp *ut;
+
+ setutent();
+
while((ut = getutent())) {
#ifdef USER_PROCESS
if (ut->ut_type != USER_PROCESS) {
@@ -293,6 +368,7 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
continue;
}
if ((pl->login_limit_def == LIMITS_DEF_ALLGROUP)
+ && pl->login_group != NULL
&& !pam_modutil_user_in_group_nam_nam(pamh, user, pl->login_group)) {
continue;
}
@@ -309,6 +385,7 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
}
}
endutent();
+#endif
if (count > limit) {
if (name) {
pam_syslog(pamh, LOG_NOTICE,
@@ -321,7 +398,8 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
return 0;
}
-static const char *lnames[RLIM_NLIMITS] = {
+#ifdef __linux__
+static const char *const lnames[RLIM_NLIMITS] = {
[RLIMIT_CPU] = "Max cpu time",
[RLIMIT_FSIZE] = "Max file size",
[RLIMIT_DATA] = "Max data size",
@@ -387,15 +465,16 @@ static rlim_t str2rlim_t(char *value) {
pos--; \
while (pos && line[pos] != ' ') pos--; \
if (!pos) continue; \
- item = line + pos + 1; \
+ (item) = line + pos + 1; \
}
static void parse_kernel_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl)
{
- int i, maxlen = 0;
+ int i;
FILE *limitsfile;
const char *proclimits = "/proc/1/limits";
- char line[256];
+ char *line = NULL;
+ size_t maxlen = 0, n = 0;
char *hard, *soft, *name;
if (!(limitsfile = fopen(proclimits, "r"))) {
@@ -403,8 +482,8 @@ static void parse_kernel_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int
return;
}
- while (fgets(line, 256, limitsfile)) {
- int pos = strlen(line);
+ while (getline(&line, &n, limitsfile) != -1) {
+ size_t pos = strlen(line);
if (pos < 2) continue;
/* drop trailing newline */
@@ -448,8 +527,10 @@ static void parse_kernel_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int
pl->limits[i].src_soft = LIMITS_DEF_KERNEL;
pl->limits[i].src_hard = LIMITS_DEF_KERNEL;
}
+ free(line);
fclose(limitsfile);
}
+#endif
static int init_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl)
{
@@ -492,6 +573,7 @@ static int init_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl)
retval = !PAM_SUCCESS;
pl->login_limit = -2;
pl->login_limit_def = LIMITS_DEF_NONE;
+ pl->login_group = NULL;
return retval;
}
@@ -504,14 +586,16 @@ static int init_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl)
static int
value_from_file(const char *pathname, rlim_t *valuep)
{
- char buf[128];
FILE *fp;
int retval;
retval = 0;
if ((fp = fopen(pathname, "r")) != NULL) {
- if (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *buf = NULL;
+ size_t n = 0;
+
+ if (getline(&buf, &n, fp) != -1) {
char *endptr;
unsigned long long value;
@@ -525,6 +609,7 @@ value_from_file(const char *pathname, rlim_t *valuep)
}
}
+ free(buf);
fclose(fp);
}
@@ -762,7 +847,6 @@ process_limit (const pam_handle_t *pamh, int source, const char *lim_type,
}
}
}
- return;
}
static int
@@ -814,11 +898,66 @@ parse_uid_range(pam_handle_t *pamh, const char *domain,
}
static int
+set_if_null(char **dest, char *def)
+{
+ if (*dest == NULL) {
+ *dest = def;
+ return 0;
+ }
+ return 1;
+}
+
+static char *
+trim(char *s)
+{
+ char *p;
+
+ if (s == NULL)
+ return NULL;
+
+ while (*s == ' ' || *s == '\t')
+ s++;
+
+ if (*s == '\0')
+ return NULL;
+
+ p = s + strlen(s) - 1;
+ while (p >= s && (*p == ' ' || *p == '\t'))
+ *p-- = '\0';
+ return s;
+}
+
+static int
+split(char *line, char **domain, char **ltype, char **item, char **value)
+{
+ char *blank, *saveptr;
+ int count;
+
+ blank = line + strlen(line);
+ saveptr = NULL;
+
+ *domain = strtok_r(line, " \t", &saveptr);
+ *ltype = strtok_r(NULL, " \t", &saveptr);
+ *item = strtok_r(NULL, " \t", &saveptr);
+ *value = trim(strtok_r(NULL, "", &saveptr));
+
+ count = 0;
+ count += set_if_null(domain, blank);
+ count += set_if_null(ltype, blank);
+ count += set_if_null(item, blank);
+ count += set_if_null(value, blank);
+
+ return count;
+}
+
+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, const int conf_file_set_by_user)
{
FILE *fil;
- char buf[LINE_LENGTH];
+ char *buf = NULL;
+ size_t n = 0;
+ unsigned long long lineno = 0;
/* check for the conf_file */
if (ctrl & PAM_DEBUG_ARG)
@@ -835,20 +974,18 @@ parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
}
/* start the show */
- while (fgets(buf, LINE_LENGTH, fil) != NULL) {
- char domain[LINE_LENGTH];
- char ltype[LINE_LENGTH];
- char item[LINE_LENGTH];
- char value[LINE_LENGTH];
+ while (getline(&buf, &n, fil) != -1) {
+ char *domain, *ltype, *item, *value, *tptr, *line;
int i;
int rngtype;
size_t j;
- char *tptr,*line;
uid_t min_uid = (uid_t)-1, max_uid = (uid_t)-1;
+ lineno++;
+
line = buf;
/* skip the leading white space */
- while (*line && isspace(*line))
+ while (*line && isspace((unsigned char)*line))
line++;
/* Rip off the comments */
@@ -863,14 +1000,12 @@ parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
if (!strlen(line))
continue;
- domain[0] = ltype[0] = item[0] = value[0] = '\0';
-
- i = sscanf(line,"%s%s%s%s", domain, ltype, item, value);
+ i = split(line, &domain, &ltype, &item, &value);
D(("scanned line[%d]: domain[%s], ltype[%s], item[%s], value[%s]",
i, domain, ltype, item, value));
for(j=0; j < strlen(ltype); j++)
- ltype[j]=tolower(ltype[j]);
+ ltype[j]=tolower((unsigned char)ltype[j]);
if ((rngtype=parse_uid_range(pamh, domain, &min_uid, &max_uid)) < 0) {
pam_syslog(pamh, LOG_WARNING, "invalid uid range '%s' - skipped", domain);
@@ -879,11 +1014,11 @@ parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
if (i == 4) { /* a complete line */
for(j=0; j < strlen(item); j++)
- item[j]=tolower(item[j]);
+ item[j]=tolower((unsigned char)item[j]);
for(j=0; j < strlen(value); j++)
- value[j]=tolower(value[j]);
+ value[j]=tolower((unsigned char)value[j]);
- if (strcmp(uname, domain) == 0) /* this user have a limit */
+ if (strcmp(uname, domain) == 0) /* this user has a limit */
process_limit(pamh, LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
else if (domain[0]=='@') {
if (ctrl & PAM_DEBUG_ARG) {
@@ -923,7 +1058,8 @@ parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
process_limit(pamh, LIMITS_DEF_ALL, ltype, item, value, ctrl,
pl);
else if (pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1)) {
- strcpy(pl->login_group, domain+1);
+ free(pl->login_group);
+ pl->login_group = strdup(domain+1);
process_limit(pamh, LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
pl);
}
@@ -932,8 +1068,8 @@ parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
if (pam_modutil_user_in_group_nam_gid(pamh, uname, (gid_t)max_uid)) {
struct group *grp;
grp = pam_modutil_getgrgid(pamh, (gid_t)max_uid);
- strncpy(pl->login_group, grp->gr_name, sizeof(pl->login_group));
- pl->login_group[sizeof(pl->login_group)-1] = '\0';
+ free(pl->login_group);
+ pl->login_group = strdup(grp->gr_name);
process_limit(pamh, LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
pl);
}
@@ -1011,12 +1147,15 @@ parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid,
pam_syslog(pamh, LOG_DEBUG, "no limits for '%s'", uname);
}
}
+ free(buf);
fclose(fil);
return PAM_IGNORE;
} else {
- pam_syslog(pamh, LOG_WARNING, "invalid line '%s' - skipped", line);
+ pam_syslog(pamh, LOG_WARNING, "invalid line %llu in '%s' - skipped",
+ lineno, pl->conf_file);
}
}
+ free(buf);
fclose(fil);
return PAM_SUCCESS;
}
@@ -1078,16 +1217,20 @@ static int setup_limits(pam_handle_t *pamh,
}
if (pl->nonewprivs) {
+#ifdef __linux__
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
pam_syslog(pamh, LOG_ERR, "Could not set prctl(PR_SET_NO_NEW_PRIVS): %m");
retval |= LIMIT_ERR;
}
+#else
+ pam_syslog(pamh, LOG_INFO, "Setting 'nonewprivs' not supported on this OS");
+#endif
}
return retval;
}
-/* --- evaluting all files in VENDORDIR/security/limits.d and /etc/security/limits.d --- */
+/* --- evaluating all files in VENDORDIR/security/limits.d and /etc/security/limits.d --- */
static const char *
base_name(const char *path)
{
@@ -1188,6 +1331,7 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
int ctrl;
struct pam_limit_s plstruct;
struct pam_limit_s *pl = &plstruct;
+ char *free_filename = NULL;
D(("called."));
@@ -1232,6 +1376,7 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
ctrl, pl, conf_file_set_by_user);
if (retval == PAM_IGNORE) {
D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
+ free(pl->login_group);
return PAM_SUCCESS;
}
if (retval != PAM_SUCCESS || conf_file_set_by_user)
@@ -1247,13 +1392,19 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
if (retval != PAM_SUCCESS)
break;
}
- for (i = 0; filename_list[i] != NULL; i++)
- free(filename_list[i]);
+ for (i = 0; filename_list[i] != NULL; i++) {
+ if (filename_list[i] == pl->conf_file)
+ free_filename = filename_list[i];
+ else
+ free(filename_list[i]);
+ }
free(filename_list);
}
if (retval == PAM_IGNORE) {
D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
+ free(free_filename);
+ free(pl->login_group);
return PAM_SUCCESS;
}
@@ -1261,10 +1412,14 @@ out:
if (retval != PAM_SUCCESS)
{
pam_syslog(pamh, LOG_ERR, "error parsing the configuration file: '%s' ", pl->conf_file);
+ free(free_filename);
+ free(pl->login_group);
return retval;
}
retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, pl);
+ free(free_filename);
+ free(pl->login_group);
if (retval & LOGIN_ERR)
pam_error(pamh, _("There were too many logins for '%s'."),
pwd->pw_name);