summaryrefslogtreecommitdiffstats
path: root/libmisc/limits.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:46:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:46:53 +0000
commit19da58be2d9359a9641381feb559be0b918ef710 (patch)
tree109724175f07436696f51b14b5abbd3f4d704d6d /libmisc/limits.c
parentInitial commit. (diff)
downloadshadow-19da58be2d9359a9641381feb559be0b918ef710.tar.xz
shadow-19da58be2d9359a9641381feb559be0b918ef710.zip
Adding upstream version 1:4.13+dfsg1.upstream/1%4.13+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--libmisc/limits.c589
1 files changed, 589 insertions, 0 deletions
diff --git a/libmisc/limits.c b/libmisc/limits.c
new file mode 100644
index 0000000..fea85fe
--- /dev/null
+++ b/libmisc/limits.c
@@ -0,0 +1,589 @@
+/*
+ * SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
+ * SPDX-FileCopyrightText: 1996 - 1999, Marek Michałkiewicz
+ * SPDX-FileCopyrightText: 2003 - 2006, Tomasz Kłoczko
+ * SPDX-FileCopyrightText: 2007 - 2010, Nicolas François
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/*
+ * Separated from setup.c. --marekm
+ * Resource limits thanks to Cristian Gafton.
+ * Enhancements of resource limit code by Thomas Orgis <thomas@orgis.org>
+ */
+
+#include <config.h>
+
+#ifndef USE_PAM
+
+#ident "$Id$"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <ctype.h>
+#include "prototypes.h"
+#include "defines.h"
+#include <pwd.h>
+#include "getdef.h"
+#include "shadowlog.h"
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#define LIMITS
+#endif
+#ifdef LIMITS
+#ifndef LIMITS_FILE
+#define LIMITS_FILE "/etc/limits"
+#endif
+#define LOGIN_ERROR_RLIMIT 1
+#define LOGIN_ERROR_LOGIN 2
+/* Set a limit on a resource */
+/*
+ * rlimit - RLIMIT_XXXX
+ * value - string value to be read
+ * multiplier - value*multiplier is the actual limit
+ */
+static int setrlimit_value (unsigned int resource,
+ const char *value,
+ unsigned int multiplier)
+{
+ struct rlimit rlim;
+ rlim_t limit;
+
+ /* The "-" is special, not belonging to a strange negative limit.
+ * It is infinity, in a controlled way.
+ */
+ if ('-' == value[0]) {
+ limit = RLIM_INFINITY;
+ }
+ else {
+ /* We cannot use getlong here because it fails when there
+ * is more to the value than just this number!
+ * Also, we are limited to base 10 here (hex numbers will not
+ * work with the limit string parser as is anyway)
+ */
+ char *endptr;
+ long longlimit = strtol (value, &endptr, 10);
+ if ((0 == longlimit) && (value == endptr)) {
+ /* No argument at all. No-op.
+ * FIXME: We could instead throw an error, though.
+ */
+ return 0;
+ }
+ longlimit *= multiplier;
+ limit = (rlim_t)longlimit;
+ if (longlimit != limit)
+ {
+ /* FIXME: Again, silent error handling...
+ * Wouldn't screaming make more sense?
+ */
+ return 0;
+ }
+ }
+
+ rlim.rlim_cur = limit;
+ rlim.rlim_max = limit;
+ if (setrlimit (resource, &rlim) != 0) {
+ return LOGIN_ERROR_RLIMIT;
+ }
+ return 0;
+}
+
+
+static int set_prio (const char *value)
+{
+ long prio;
+
+ if ( (getlong (value, &prio) == 0)
+ || (prio != (int) prio)) {
+ return 0;
+ }
+ if (setpriority (PRIO_PROCESS, 0, (int) prio) != 0) {
+ return LOGIN_ERROR_RLIMIT;
+ }
+ return 0;
+}
+
+
+static int set_umask (const char *value)
+{
+ unsigned long int mask;
+
+ if ( (getulong (value, &mask) == 0)
+ || (mask != (mode_t) mask)) {
+ return 0;
+ }
+
+ (void) umask ((mode_t) mask);
+ return 0;
+}
+
+
+/* Counts the number of user logins and check against the limit */
+static int check_logins (const char *name, const char *maxlogins)
+{
+#ifdef USE_UTMPX
+ struct utmpx *ut;
+#else /* !USE_UTMPX */
+ struct utmp *ut;
+#endif /* !USE_UTMPX */
+ unsigned long limit, count;
+
+ if (getulong (maxlogins, &limit) == 0) {
+ return 0;
+ }
+
+ if (0 == limit) { /* maximum 0 logins ? */
+ SYSLOG ((LOG_WARN, "No logins allowed for `%s'\n", name));
+ return LOGIN_ERROR_LOGIN;
+ }
+
+ count = 0;
+#ifdef USE_UTMPX
+ setutxent ();
+ while ((ut = getutxent ()))
+#else /* !USE_UTMPX */
+ setutent ();
+ while ((ut = getutent ()))
+#endif /* !USE_UTMPX */
+ {
+ if (USER_PROCESS != ut->ut_type) {
+ continue;
+ }
+ if ('\0' == ut->ut_user[0]) {
+ continue;
+ }
+ if (strncmp (name, ut->ut_user, sizeof (ut->ut_user)) != 0) {
+ continue;
+ }
+ count++;
+ if (count > limit) {
+ break;
+ }
+ }
+#ifdef USE_UTMPX
+ endutxent ();
+#else /* !USE_UTMPX */
+ endutent ();
+#endif /* !USE_UTMPX */
+ /*
+ * This is called after setutmp(), so the number of logins counted
+ * includes the user who is currently trying to log in.
+ */
+ if (count > limit) {
+ SYSLOG ((LOG_WARN,
+ "Too many logins (max %lu) for %s\n",
+ limit, name));
+ return LOGIN_ERROR_LOGIN;
+ }
+ return 0;
+}
+
+/* Function setup_user_limits - checks/set limits for the current login
+ * Original idea from Joel Katz's lshell. Ported to shadow-login
+ * by Cristian Gafton - gafton@sorosis.ro
+ *
+ * We are passed a string of the form ('BASH' constants for ulimit)
+ * [Aa][Cc][Dd][Ff][Mm][Nn][Rr][Ss][Tt][Uu][Ll][Pp][Ii][Oo]
+ * (eg. 'C2F256D2048N5' or 'C2 F256 D2048 N5')
+ * where:
+ * [Aa]: a = RLIMIT_AS max address space (KB)
+ * [Cc]: c = RLIMIT_CORE max core file size (KB)
+ * [Dd]: d = RLIMIT_DATA max data size (KB)
+ * [Ff]: f = RLIMIT_FSIZE max file size (KB)
+ * [Ii]: i = RLIMIT_NICE max nice value (0..39 translates to 20..-19)
+ * [Kk]: k = file creation masK (umask)
+ * [Ll]: l = max number of logins for this user
+ * [Mm]: m = RLIMIT_MEMLOCK max locked-in-memory address space (KB)
+ * [Nn]: n = RLIMIT_NOFILE max number of open files
+ * [Oo]: o = RLIMIT_RTPRIO max real time priority (linux/sched.h 0..MAX_RT_PRIO)
+ * [Pp]: p = process priority -20..20 (negative = high, positive = low)
+ * [Rr]: r = RLIMIT_RSS max resident set size (KB)
+ * [Ss]: s = RLIMIT_STACK max stack size (KB)
+ * [Tt]: t = RLIMIT_CPU max CPU time (MIN)
+ * [Uu]: u = RLIMIT_NPROC max number of processes
+ *
+ * NOTE: Remember to extend the "no-limits" string below when adding a new
+ * limit...
+ *
+ * Return value:
+ * 0 = okay, of course
+ * LOGIN_ERROR_RLIMIT = error setting some RLIMIT
+ * LOGIN_ERROR_LOGIN = error - too many logins for this user
+ *
+ * buf - the limits string
+ * name - the username
+ */
+static int do_user_limits (const char *buf, const char *name)
+{
+ const char *pp;
+ int retval = 0;
+ bool reported = false;
+
+ pp = buf;
+ /* Skip leading whitespace. */
+ while ((' ' == *pp) || ('\t' == *pp)) {
+ pp++;
+ }
+
+ /* The special limit string "-" results in no limit for all known
+ * limits.
+ * We achieve that by parsing a full limit string, parts of it
+ * being ignored if a limit type is not known to the system.
+ * Though, there will be complaining for unknown limit types.
+ */
+ if (strcmp (pp, "-") == 0) {
+ /* Remember to extend this, too, when adding new limits!
+ * Oh... but "unlimited" does not make sense for umask,
+ * or does it? (K-)
+ */
+ pp = "A- C- D- F- I- L- M- N- O- P- R- S- T- U-";
+ }
+
+ while ('\0' != *pp) {
+ switch (*pp++) {
+#ifdef RLIMIT_AS
+ case 'a':
+ case 'A':
+ /* RLIMIT_AS - max address space (KB) */
+ retval |= setrlimit_value (RLIMIT_AS, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_CORE
+ case 'c':
+ case 'C':
+ /* RLIMIT_CORE - max core file size (KB) */
+ retval |= setrlimit_value (RLIMIT_CORE, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_DATA
+ case 'd':
+ case 'D':
+ /* RLIMIT_DATA - max data size (KB) */
+ retval |= setrlimit_value (RLIMIT_DATA, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_FSIZE
+ case 'f':
+ case 'F':
+ /* RLIMIT_FSIZE - Maximum filesize (KB) */
+ retval |= setrlimit_value (RLIMIT_FSIZE, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_NICE
+ case 'i':
+ case 'I':
+ /* RLIMIT_NICE - max scheduling priority (0..39) */
+ retval |= setrlimit_value (RLIMIT_NICE, pp, 1);
+ break;
+#endif
+ case 'k':
+ case 'K':
+ retval |= set_umask (pp);
+ break;
+ case 'l':
+ case 'L':
+ /* LIMIT the number of concurrent logins */
+ retval |= check_logins (name, pp);
+ break;
+#ifdef RLIMIT_MEMLOCK
+ case 'm':
+ case 'M':
+ /* RLIMIT_MEMLOCK - max locked-in-memory address space (KB) */
+ retval |= setrlimit_value (RLIMIT_MEMLOCK, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_NOFILE
+ case 'n':
+ case 'N':
+ /* RLIMIT_NOFILE - max number of open files */
+ retval |= setrlimit_value (RLIMIT_NOFILE, pp, 1);
+ break;
+#endif
+#ifdef RLIMIT_RTPRIO
+ case 'o':
+ case 'O':
+ /* RLIMIT_RTPRIO - max real time priority (0..MAX_RT_PRIO) */
+ retval |= setrlimit_value (RLIMIT_RTPRIO, pp, 1);
+ break;
+#endif
+ case 'p':
+ case 'P':
+ retval |= set_prio (pp);
+ break;
+#ifdef RLIMIT_RSS
+ case 'r':
+ case 'R':
+ /* RLIMIT_RSS - max resident set size (KB) */
+ retval |= setrlimit_value (RLIMIT_RSS, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_STACK
+ case 's':
+ case 'S':
+ /* RLIMIT_STACK - max stack size (KB) */
+ retval |= setrlimit_value (RLIMIT_STACK, pp, 1024);
+ break;
+#endif
+#ifdef RLIMIT_CPU
+ case 't':
+ case 'T':
+ /* RLIMIT_CPU - max CPU time (MIN) */
+ retval |= setrlimit_value (RLIMIT_CPU, pp, 60);
+ break;
+#endif
+#ifdef RLIMIT_NPROC
+ case 'u':
+ case 'U':
+ /* RLIMIT_NPROC - max number of processes */
+ retval |= setrlimit_value (RLIMIT_NPROC, pp, 1);
+ break;
+#endif
+ default:
+ /* Only report invalid strings once */
+ /* Note: A string can be invalid just because a
+ * specific (theoretically valid) setting is not
+ * supported by this build.
+ * It is just a warning in syslog anyway. The line
+ * is still processed
+ */
+ if (!reported) {
+ SYSLOG ((LOG_WARN,
+ "Invalid limit string: '%s'",
+ pp-1));
+ reported = true;
+ retval |= LOGIN_ERROR_RLIMIT;
+ }
+ }
+ /* After parsing one limit setting (or just complaining
+ * about it), one still needs to skip its argument to
+ * prevent a bogus warning on trying to parse that as
+ * limit specification.
+ * So, let's skip all digits, "-" and our limited set of
+ * whitespace.
+ */
+ while ( isdigit (*pp)
+ || ('-' == *pp)
+ || (' ' == *pp)
+ || ('\t' ==*pp)) {
+ pp++;
+ }
+ }
+ return retval;
+}
+
+/* Check if user uname is in the group gname.
+ * Can I be sure that gr_mem contains no UID as string?
+ * Returns true when user is in the group, false when not.
+ * Any error is treated as false.
+ */
+static bool user_in_group (const char *uname, const char *gname)
+{
+ struct group *groupdata;
+
+ if (uname == NULL || gname == NULL) {
+ return false;
+ }
+
+ /* We are not claiming to be re-entrant!
+ * In case of paranoia or a multithreaded login program,
+ * one needs to add some mess for getgrnam_r. */
+ groupdata = getgrnam (gname);
+ if (NULL == groupdata) {
+ SYSLOG ((LOG_WARN, "Nonexisting group `%s' in limits file.",
+ gname));
+ return false;
+ }
+
+ return is_on_list (groupdata->gr_mem, uname);
+}
+
+static int setup_user_limits (const char *uname)
+{
+ FILE *fil;
+ char buf[1024];
+ char name[1024];
+ char limits[1024];
+ char deflimits[1024];
+ char tempbuf[1024];
+
+ /* init things */
+ memzero (buf, sizeof (buf));
+ memzero (name, sizeof (name));
+ memzero (limits, sizeof (limits));
+ memzero (deflimits, sizeof (deflimits));
+ memzero (tempbuf, sizeof (tempbuf));
+
+ /* start the checks */
+ fil = fopen (LIMITS_FILE, "r");
+ if (fil == NULL) {
+ return 0;
+ }
+ /* The limits file have the following format:
+ * - '#' (comment) chars only as first chars on a line;
+ * - username must start on first column (or *, or @group)
+ *
+ * FIXME: A better (smarter) checking should be done
+ */
+ while (fgets (buf, 1024, fil) != NULL) {
+ if (('#' == buf[0]) || ('\n' == buf[0])) {
+ continue;
+ }
+ memzero (tempbuf, sizeof (tempbuf));
+ /* a valid line should have a username, then spaces,
+ * then limits
+ * we allow the format:
+ * username L2 D2048 R4096
+ * where spaces={' ',\t}. Also, we reject invalid limits.
+ * Imposing a limit should be done with care, so a wrong
+ * entry means no care anyway :-).
+ *
+ * A '-' as a limits strings means no limits
+ *
+ * The username can also be:
+ * '*': the default limits (only the last is taken into
+ * account)
+ * @group: the limit applies to the members of the group
+ *
+ * To clarify: The first entry with matching user name rules,
+ * everything after it is ignored. If there is no user entry,
+ * the last encountered entry for a matching group rules.
+ * If there is no matching group entry, the default limits rule.
+ */
+ if (sscanf (buf, "%s%[ACDFIKLMNOPRSTUacdfiklmnoprstu0-9 \t-]",
+ name, tempbuf) == 2) {
+ if (strcmp (name, uname) == 0) {
+ strcpy (limits, tempbuf);
+ break;
+ } else if (strcmp (name, "*") == 0) {
+ strcpy (deflimits, tempbuf);
+ } else if (name[0] == '@') {
+ /* If the user is in the group, the group
+ * limits apply unless later a line for
+ * the specific user is found.
+ */
+ if (user_in_group (uname, name+1)) {
+ strcpy (limits, tempbuf);
+ }
+ }
+ }
+ }
+ (void) fclose (fil);
+ if (limits[0] == '\0') {
+ /* no user specific limits */
+ if (deflimits[0] == '\0') { /* no default limits */
+ return 0;
+ }
+ strcpy (limits, deflimits); /* use the default limits */
+ }
+ return do_user_limits (limits, uname);
+}
+#endif /* LIMITS */
+
+
+static void setup_usergroups (const struct passwd *info)
+{
+ const struct group *grp;
+
+/*
+ * if not root, and UID == GID, and username is the same as primary
+ * group name, set umask group bits to be the same as owner bits
+ * (examples: 022 -> 002, 077 -> 007).
+ */
+ if ((0 != info->pw_uid) && (info->pw_uid == info->pw_gid)) {
+ /* local, no need for xgetgrgid */
+ grp = getgrgid (info->pw_gid);
+ if ( (NULL != grp)
+ && (strcmp (info->pw_name, grp->gr_name) == 0)) {
+ mode_t tmpmask;
+ tmpmask = umask (0777);
+ tmpmask = (tmpmask & ~070) | ((tmpmask >> 3) & 070);
+ (void) umask (tmpmask);
+ }
+ }
+}
+
+/*
+ * set the process nice, ulimit, and umask from the password file entry
+ */
+
+void setup_limits (const struct passwd *info)
+{
+ char *cp;
+
+ if (getdef_bool ("USERGROUPS_ENAB")) {
+ setup_usergroups (info);
+ }
+
+ /*
+ * See if the GECOS field contains values for NICE, UMASK or ULIMIT.
+ * If this feature is enabled in /etc/login.defs, we make those
+ * values the defaults for this login session.
+ */
+
+ if (getdef_bool ("QUOTAS_ENAB")) {
+#ifdef LIMITS
+ if (info->pw_uid != 0) {
+ if ((setup_user_limits (info->pw_name) & LOGIN_ERROR_LOGIN) != 0) {
+ (void) fputs (_("Too many logins.\n"), log_get_logfd());
+ (void) sleep (2); /* XXX: Should be FAIL_DELAY */
+ exit (EXIT_FAILURE);
+ }
+ }
+#endif
+ for (cp = info->pw_gecos; cp != NULL; cp = strchr (cp, ',')) {
+ if (',' == *cp) {
+ cp++;
+ }
+
+ if (strncmp (cp, "pri=", 4) == 0) {
+ long int inc;
+ if ( (getlong (cp + 4, &inc) == 1)
+ && (inc >= -20) && (inc <= 20)) {
+ errno = 0;
+ if ( (nice ((int) inc) != -1)
+ || (0 != errno)) {
+ continue;
+ }
+ }
+
+ /* Failed to parse or failed to nice() */
+ SYSLOG ((LOG_WARN,
+ "Can't set the nice value for user %s",
+ info->pw_name));
+
+ continue;
+ }
+ if (strncmp (cp, "ulimit=", 7) == 0) {
+ long int blocks;
+ if ( (getlong (cp + 7, &blocks) == 0)
+ || (blocks != (int) blocks)
+ || (set_filesize_limit ((int) blocks) != 0)) {
+ SYSLOG ((LOG_WARN,
+ "Can't set the ulimit for user %s",
+ info->pw_name));
+ }
+ continue;
+ }
+ if (strncmp (cp, "umask=", 6) == 0) {
+ unsigned long int mask;
+ if ( (getulong (cp + 6, &mask) == 0)
+ || (mask != (mode_t) mask)) {
+ SYSLOG ((LOG_WARN,
+ "Can't set umask value for user %s",
+ info->pw_name));
+ } else {
+ (void) umask ((mode_t) mask);
+ }
+
+ continue;
+ }
+ }
+ }
+}
+
+#else /* !USE_PAM */
+extern int errno; /* warning: ANSI C forbids an empty source file */
+#endif /* !USE_PAM */
+