summaryrefslogtreecommitdiff
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.c367
1 files changed, 330 insertions, 37 deletions
diff --git a/modules/pam_limits/pam_limits.c b/modules/pam_limits/pam_limits.c
index f446f9e3..c1810e07 100644
--- a/modules/pam_limits/pam_limits.c
+++ b/modules/pam_limits/pam_limits.c
@@ -51,9 +51,16 @@
#define LIMITS_DEF_USER 0 /* limit was set by an 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 */
-#define LIMITS_DEF_ALL 3 /* limit was set by an default entry */
-#define LIMITS_DEF_DEFAULT 4 /* limit was set by an default entry */
-#define LIMITS_DEF_NONE 5 /* this limit was not set yet */
+#define LIMITS_DEF_ALL 3 /* limit was set by an all entry */
+#define LIMITS_DEF_DEFAULT 4 /* limit was set by a default entry */
+#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_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[] = {
"USER",
@@ -61,6 +68,7 @@ static const char *limits_def_names[] = {
"ALLGROUP",
"ALL",
"DEFAULT",
+ "KERNEL",
"NONE",
NULL
};
@@ -103,9 +111,9 @@ struct pam_limit_s {
/* argument parsing */
#define PAM_DEBUG_ARG 0x0001
-#define PAM_DO_SETREUID 0x0002
#define PAM_UTMP_EARLY 0x0004
#define PAM_NO_AUDIT 0x0008
+#define PAM_SET_ALL 0x0010
/* Limits from globbed files. */
#define LIMITS_CONF_GLOB LIMITS_FILE_DIR
@@ -127,12 +135,12 @@ _pam_parse (const pam_handle_t *pamh, int argc, const char **argv,
ctrl |= PAM_DEBUG_ARG;
} else if (!strncmp(*argv,"conf=",5)) {
pl->conf_file = *argv+5;
- } else if (!strncmp(*argv,"change_uid",10)) {
- ctrl |= PAM_DO_SETREUID;
} else if (!strcmp(*argv,"utmp_early")) {
ctrl |= PAM_UTMP_EARLY;
} else if (!strcmp(*argv,"noaudit")) {
ctrl |= PAM_NO_AUDIT;
+ } else if (!strcmp(*argv,"set_all")) {
+ ctrl |= PAM_SET_ALL;
} else {
pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
}
@@ -289,7 +297,140 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
return 0;
}
-static int init_limits(struct pam_limit_s *pl)
+static const char *lnames[RLIM_NLIMITS] = {
+ [RLIMIT_CPU] = "Max cpu time",
+ [RLIMIT_FSIZE] = "Max file size",
+ [RLIMIT_DATA] = "Max data size",
+ [RLIMIT_STACK] = "Max stack size",
+ [RLIMIT_CORE] = "Max core file size",
+ [RLIMIT_RSS] = "Max resident set",
+ [RLIMIT_NPROC] = "Max processes",
+ [RLIMIT_NOFILE] = "Max open files",
+ [RLIMIT_MEMLOCK] = "Max locked memory",
+#ifdef RLIMIT_AS
+ [RLIMIT_AS] = "Max address space",
+#endif
+#ifdef RLIMIT_LOCKS
+ [RLIMIT_LOCKS] = "Max file locks",
+#endif
+#ifdef RLIMIT_SIGPENDING
+ [RLIMIT_SIGPENDING] = "Max pending signals",
+#endif
+#ifdef RLIMIT_MSGQUEUE
+ [RLIMIT_MSGQUEUE] = "Max msgqueue size",
+#endif
+#ifdef RLIMIT_NICE
+ [RLIMIT_NICE] = "Max nice priority",
+#endif
+#ifdef RLIMIT_RTPRIO
+ [RLIMIT_RTPRIO] = "Max realtime priority",
+#endif
+#ifdef RLIMIT_RTTIME
+ [RLIMIT_RTTIME] = "Max realtime timeout",
+#endif
+};
+
+static int str2rlimit(char *name) {
+ int i;
+ if (!name || *name == '\0')
+ return -1;
+ for(i = 0; i < RLIM_NLIMITS; i++) {
+ if (strcmp(name, lnames[i]) == 0) return i;
+ }
+ return -1;
+}
+
+static rlim_t str2rlim_t(char *value) {
+ unsigned long long rlimit = 0;
+
+ if (!value) return (rlim_t)rlimit;
+ if (strcmp(value, "unlimited") == 0) {
+ return RLIM_INFINITY;
+ }
+ rlimit = strtoull(value, NULL, 10);
+ return (rlim_t)rlimit;
+}
+
+#define LIMITS_SKIP_WHITESPACE { \
+ /* step backwards over spaces */ \
+ pos--; \
+ while (pos && line[pos] == ' ') pos--; \
+ if (!pos) continue; \
+ line[pos+1] = '\0'; \
+}
+#define LIMITS_MARK_ITEM(item) { \
+ /* step backwards over non-spaces */ \
+ pos--; \
+ while (pos && line[pos] != ' ') pos--; \
+ if (!pos) continue; \
+ item = line + pos + 1; \
+}
+
+static void parse_kernel_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl)
+{
+ int i, maxlen = 0;
+ FILE *limitsfile;
+ const char *proclimits = "/proc/1/limits";
+ char line[256];
+ char *units, *hard, *soft, *name;
+
+ if (!(limitsfile = fopen(proclimits, "r"))) {
+ pam_syslog(pamh, LOG_WARNING, "Could not read %s (%s), using PAM defaults", proclimits, strerror(errno));
+ return;
+ }
+
+ while (fgets(line, 256, limitsfile)) {
+ int pos = strlen(line);
+ if (pos < 2) continue;
+
+ /* drop trailing newline */
+ if (line[pos-1] == '\n') {
+ pos--;
+ line[pos] = '\0';
+ }
+
+ /* determine formatting boundry of limits report */
+ if (!maxlen && strncmp(line, "Limit", 5) == 0) {
+ maxlen = pos;
+ continue;
+ }
+
+ if (pos == maxlen) {
+ /* step backwards over "Units" name */
+ LIMITS_SKIP_WHITESPACE;
+ LIMITS_MARK_ITEM(units);
+ }
+ else {
+ units = "";
+ }
+
+ /* step backwards over "Hard Limit" value */
+ LIMITS_SKIP_WHITESPACE;
+ LIMITS_MARK_ITEM(hard);
+
+ /* step backwards over "Soft Limit" value */
+ LIMITS_SKIP_WHITESPACE;
+ LIMITS_MARK_ITEM(soft);
+
+ /* step backwards over name of limit */
+ LIMITS_SKIP_WHITESPACE;
+ name = line;
+
+ i = str2rlimit(name);
+ if (i < 0 || i >= RLIM_NLIMITS) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_DEBUG, "Unknown kernel rlimit '%s' ignored", name);
+ continue;
+ }
+ pl->limits[i].limit.rlim_cur = str2rlim_t(soft);
+ pl->limits[i].limit.rlim_max = str2rlim_t(hard);
+ pl->limits[i].src_soft = LIMITS_DEF_KERNEL;
+ pl->limits[i].src_hard = LIMITS_DEF_KERNEL;
+ }
+ fclose(limitsfile);
+}
+
+static int init_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl)
{
int i;
int retval = PAM_SUCCESS;
@@ -310,6 +451,20 @@ static int init_limits(struct pam_limit_s *pl)
}
}
+#ifdef __linux__
+ if (ctrl & PAM_SET_ALL) {
+ parse_kernel_limits(pamh, pl, ctrl);
+
+ for(i = 0; i < RLIM_NLIMITS; i++) {
+ if (pl->limits[i].supported &&
+ (pl->limits[i].src_soft == LIMITS_DEF_NONE ||
+ pl->limits[i].src_hard == LIMITS_DEF_NONE)) {
+ pam_syslog(pamh, LOG_WARNING, "Did not find kernel RLIMIT for %s, using PAM default", rlimit2str(i));
+ }
+ }
+ }
+#endif
+
errno = 0;
pl->priority = getpriority (PRIO_PROCESS, 0);
if (pl->priority == -1 && errno != 0)
@@ -523,8 +678,57 @@ process_limit (const pam_handle_t *pamh, int source, const char *lim_type,
return;
}
-static int parse_config_file(pam_handle_t *pamh, const char *uname, int ctrl,
- struct pam_limit_s *pl)
+static int
+parse_uid_range(pam_handle_t *pamh, const char *domain,
+ uid_t *min_uid, uid_t *max_uid)
+{
+ const char *range = domain;
+ char *pmax;
+ char *endptr;
+ int rv = LIMIT_RANGE_MM;
+
+ if ((pmax=strchr(range, ':')) == NULL)
+ return LIMIT_RANGE_NONE;
+ ++pmax;
+
+ if (range[0] == '@' || range[0] == '%')
+ ++range;
+
+ if (range[0] == ':')
+ rv = LIMIT_RANGE_ONE;
+ else {
+ errno = 0;
+ *min_uid = strtoul (range, &endptr, 10);
+ if (errno != 0 || (range == endptr) || *endptr != ':') {
+ pam_syslog(pamh, LOG_DEBUG,
+ "wrong min_uid/gid value in '%s'", domain);
+ return LIMIT_RANGE_ERR;
+ }
+ }
+
+ if (*pmax == '\0') {
+ if (rv == LIMIT_RANGE_ONE)
+ return LIMIT_RANGE_ERR;
+ else
+ return LIMIT_RANGE_MIN;
+ }
+
+ errno = 0;
+ *max_uid = strtoul (pmax, &endptr, 10);
+ if (errno != 0 || (pmax == endptr) || *endptr != '\0') {
+ pam_syslog(pamh, LOG_DEBUG,
+ "wrong max_uid/gid value in '%s'", domain);
+ return LIMIT_RANGE_ERR;
+ }
+
+ if (rv == LIMIT_RANGE_ONE)
+ *min_uid = *max_uid;
+ return rv;
+}
+
+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)
{
FILE *fil;
char buf[LINE_LENGTH];
@@ -546,8 +750,10 @@ static int parse_config_file(pam_handle_t *pamh, const char *uname, int ctrl,
char item[LINE_LENGTH];
char value[LINE_LENGTH];
int i;
+ int rngtype;
size_t j;
char *tptr,*line;
+ uid_t min_uid = (uid_t)-1, max_uid = (uid_t)-1;
line = buf;
/* skip the leading white space */
@@ -575,6 +781,11 @@ static int parse_config_file(pam_handle_t *pamh, const char *uname, int ctrl,
for(j=0; j < strlen(ltype); j++)
ltype[j]=tolower(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);
+ continue;
+ }
+
if (i == 4) { /* a complete line */
for(j=0; j < strlen(item); j++)
item[j]=tolower(item[j]);
@@ -584,47 +795,133 @@ static int parse_config_file(pam_handle_t *pamh, const char *uname, int ctrl,
if (strcmp(uname, domain) == 0) /* this user have a limit */
process_limit(pamh, LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
else if (domain[0]=='@') {
- if (ctrl & PAM_DEBUG_ARG) {
+ if (ctrl & PAM_DEBUG_ARG) {
pam_syslog(pamh, LOG_DEBUG,
"checking if %s is in group %s",
uname, domain + 1);
- }
- if (pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1))
- process_limit(pamh, LIMITS_DEF_GROUP, ltype, item, value, ctrl,
+ }
+ switch(rngtype) {
+ case LIMIT_RANGE_NONE:
+ if (pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1))
+ process_limit(pamh, LIMITS_DEF_GROUP, ltype, item, value, ctrl,
+ pl);
+ break;
+ case LIMIT_RANGE_ONE:
+ if (pam_modutil_user_in_group_nam_gid(pamh, uname, (gid_t)max_uid))
+ process_limit(pamh, LIMITS_DEF_GROUP, ltype, item, value, ctrl,
pl);
+ break;
+ case LIMIT_RANGE_MM:
+ if (gid > (gid_t)max_uid)
+ break;
+ /* fallthrough */
+ case LIMIT_RANGE_MIN:
+ if (gid >= (gid_t)min_uid)
+ process_limit(pamh, LIMITS_DEF_GROUP, ltype, item, value, ctrl,
+ pl);
+ }
} else if (domain[0]=='%') {
- if (ctrl & PAM_DEBUG_ARG) {
+ if (ctrl & PAM_DEBUG_ARG) {
pam_syslog(pamh, LOG_DEBUG,
"checking if %s is in group %s",
uname, domain + 1);
- }
- if (strcmp(domain,"%") == 0)
- 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);
- process_limit(pamh, LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
- pl);
}
- } else if (strcmp(domain, "*") == 0)
- process_limit(pamh, LIMITS_DEF_DEFAULT, ltype, item, value, ctrl,
- pl);
+ switch(rngtype) {
+ case LIMIT_RANGE_NONE:
+ if (strcmp(domain,"%") == 0)
+ 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);
+ process_limit(pamh, LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
+ pl);
+ }
+ break;
+ case LIMIT_RANGE_ONE:
+ 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';
+ process_limit(pamh, LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
+ pl);
+ }
+ break;
+ case LIMIT_RANGE_MIN:
+ case LIMIT_RANGE_MM:
+ pam_syslog(pamh, LOG_WARNING, "range unsupported for %%group matching - ignored");
+ }
+ } else {
+ switch(rngtype) {
+ case LIMIT_RANGE_NONE:
+ if (strcmp(domain, "*") == 0)
+ process_limit(pamh, LIMITS_DEF_DEFAULT, ltype, item, value, ctrl,
+ pl);
+ break;
+ case LIMIT_RANGE_ONE:
+ if (uid != max_uid)
+ break;
+ /* fallthrough */
+ case LIMIT_RANGE_MM:
+ if (uid > max_uid)
+ break;
+ /* fallthrough */
+ case LIMIT_RANGE_MIN:
+ if (uid >= min_uid)
+ process_limit(pamh, LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
+ }
+ }
} else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */
if (strcmp(uname, domain) == 0) {
if (ctrl & PAM_DEBUG_ARG) {
pam_syslog(pamh, LOG_DEBUG, "no limits for '%s'", uname);
}
- fclose(fil);
- return PAM_IGNORE;
- } else if (domain[0] == '@' && pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1)) {
+ } else if (domain[0] == '@') {
+ switch(rngtype) {
+ case LIMIT_RANGE_NONE:
+ if (!pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1))
+ continue; /* next line */
+ break;
+ case LIMIT_RANGE_ONE:
+ if (!pam_modutil_user_in_group_nam_gid(pamh, uname, (gid_t)max_uid))
+ continue; /* next line */
+ break;
+ case LIMIT_RANGE_MM:
+ if (gid > (gid_t)max_uid)
+ continue; /* next line */
+ /* fallthrough */
+ case LIMIT_RANGE_MIN:
+ if (gid < (gid_t)min_uid)
+ continue; /* next line */
+ }
if (ctrl & PAM_DEBUG_ARG) {
pam_syslog(pamh, LOG_DEBUG,
"no limits for '%s' in group '%s'",
uname, domain+1);
}
- fclose(fil);
- return PAM_IGNORE;
+ } else {
+ switch(rngtype) {
+ case LIMIT_RANGE_NONE:
+ continue; /* next line */
+ case LIMIT_RANGE_ONE:
+ if (uid != max_uid)
+ continue; /* next line */
+ break;
+ case LIMIT_RANGE_MM:
+ if (uid > max_uid)
+ continue; /* next line */
+ /* fallthrough */
+ case LIMIT_RANGE_MIN:
+ if (uid >= min_uid)
+ break;
+ continue; /* next line */
+ }
+ if (ctrl & PAM_DEBUG_ARG) {
+ pam_syslog(pamh, LOG_DEBUG, "no limits for '%s'", uname);
+ }
}
+ fclose(fil);
+ return PAM_IGNORE;
} else {
pam_syslog(pamh, LOG_WARNING, "invalid line '%s' - skipped", line);
}
@@ -728,13 +1025,13 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
return PAM_USER_UNKNOWN;
}
- retval = init_limits(pl);
+ retval = init_limits(pamh, pl, ctrl);
if (retval != PAM_SUCCESS) {
pam_syslog(pamh, LOG_WARNING, "cannot initialize");
return PAM_ABORT;
}
- retval = parse_config_file(pamh, pwd->pw_name, ctrl, pl);
+ 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", CONF_FILE));
return PAM_SUCCESS;
@@ -758,7 +1055,7 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
/* 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, ctrl, pl);
+ 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);
@@ -777,10 +1074,6 @@ out:
return retval;
}
- if (ctrl & PAM_DO_SETREUID) {
- setreuid(pwd->pw_uid, -1);
- }
-
retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, pl);
if (retval & LOGIN_ERR)
pam_error(pamh, _("Too many logins for '%s'."), pwd->pw_name);