/* This file is part of GNU Rush. Copyright (C) 2008-2019 Sergey Poznyakoff GNU Rush is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Rush is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Rush. If not, see . */ #include #define SET_LIMIT_AS 0x0001 #define SET_LIMIT_CPU 0x0002 #define SET_LIMIT_DATA 0x0004 #define SET_LIMIT_FSIZE 0x0008 #define SET_LIMIT_NPROC 0x0010 #define SET_LIMIT_CORE 0x0020 #define SET_LIMIT_MEMLOCK 0x0040 #define SET_LIMIT_NOFILE 0x0080 #define SET_LIMIT_RSS 0x0100 #define SET_LIMIT_STACK 0x0200 #define SET_LIMIT_LOGINS 0x0400 #define SET_LIMIT_PRIO 0x0800 struct limits_rec { unsigned set; rlim_t limit_as; rlim_t limit_cpu; rlim_t limit_data; rlim_t limit_fsize; rlim_t limit_nproc; rlim_t limit_core; rlim_t limit_memlock; rlim_t limit_nofile; rlim_t limit_rss; rlim_t limit_stack; size_t limit_logins; int limit_prio; }; int do_set_limit(int rlimit, rlim_t limit) { struct rlimit rlim; debug(2, _("Setting limit %d to %lu"), rlimit, (unsigned long) limit); rlim.rlim_cur = limit; rlim.rlim_max = limit; if (setrlimit(rlimit, &rlim)) { logmsg(LOG_NOTICE, _("error setting limit: %s"), strerror(errno)); return 1; } return 0; } static int set_prio(int prio) { debug(2, _("Setting priority to %d"), prio); if (setpriority(PRIO_PROCESS, 0, prio)) { logmsg(LOG_NOTICE, _("error setting priority: %s"), strerror(errno)); return 1; } return 0; } /* Counts the number of user logins and check against the limit */ static int check_logins(const char *name, size_t limit) { size_t count = 0; struct rush_wtmp *wtmp = 0; int status; if (limit == 0) /* maximum 0 logins ? */ { debug(2, _("No logins allowed for `%s'"), name); logmsg(LOG_ERR, _("No logins allowed for `%s'"), name); return 1; } debug(3, _("counting logins for %s"), name); switch (rushdb_open(RUSH_DB, 0)) { case rushdb_result_ok: break; case rushdb_result_eof: debug(3, "%s", _("acct database is empty")); return 0; case rushdb_result_fail: logmsg(LOG_ERR, _("Cannot open database %s: %s"), RUSH_DB, rushdb_error_string); return 0; } while (rush_utmp_read(RUSH_STATUS_MAP_BIT(RUSH_STATUS_INUSE), &status, &wtmp) == 0) { if (strcmp (wtmp->user, name) == 0) { if (++count >= limit) break; } free(wtmp); wtmp = NULL; } free(wtmp); rushdb_close(); debug(3, _("counted %zu/%zu logins for %s"), count, limit, name); /* * This is called after setutmp(), so the number of logins counted * includes the user who is currently trying to log in. */ if (count >= limit) { debug(2, _("Too many logins (max %zu) for %s"), limit, name); logmsg(LOG_ERR, _("Too many logins (max %zu) for %s"), limit, name); return 1; } return 0; } int set_user_limits(const char *name, struct limits_rec *lrec) { int rc = 0; if (!lrec) return 0; debug(2, _("Setting limits for %s"), name); #if defined(RLIMIT_AS) if (lrec->set & SET_LIMIT_AS) rc |= do_set_limit(RLIMIT_AS, lrec->limit_as); #endif #if defined(RLIMIT_CPU) if (lrec->set & SET_LIMIT_CPU) rc |= do_set_limit(RLIMIT_CPU, lrec->limit_cpu); #endif #if defined(RLIMIT_DATA) if (lrec->set & SET_LIMIT_DATA) rc |= do_set_limit(RLIMIT_DATA, lrec->limit_data); #endif #if defined(RLIMIT_FSIZE) if (lrec->set & SET_LIMIT_FSIZE) rc |= do_set_limit(RLIMIT_FSIZE, lrec->limit_fsize); #endif #if defined(RLIMIT_NPROC) if (lrec->set & SET_LIMIT_NPROC) rc |= do_set_limit(RLIMIT_NPROC, lrec->limit_nproc); #endif #if defined(RLIMIT_CORE) if (lrec->set & SET_LIMIT_CORE) rc |= do_set_limit(RLIMIT_CORE, lrec->limit_core); #endif #if defined(RLIMIT_MEMLOCK) if (lrec->set & SET_LIMIT_MEMLOCK) rc |= do_set_limit(RLIMIT_MEMLOCK, lrec->limit_memlock); #endif #if defined(RLIMIT_NOFILE) if (lrec->set & SET_LIMIT_NOFILE) rc |= do_set_limit(RLIMIT_NOFILE, lrec->limit_nofile); #endif #if defined(RLIMIT_RSS) if (lrec->set & SET_LIMIT_RSS) rc |= do_set_limit(RLIMIT_RSS, lrec->limit_rss); #endif #if defined(RLIMIT_STACK) if (lrec->set & SET_LIMIT_STACK) rc |= do_set_limit(RLIMIT_STACK, lrec->limit_stack); #endif if (lrec->set & SET_LIMIT_LOGINS) rc |= check_logins(name, lrec->limit_logins); if (lrec->set & SET_LIMIT_PRIO) rc |= set_prio(lrec->limit_prio); return rc; } int getlimit(char **ptr, rlim_t *rlim, int mul) { unsigned long val; val = strtoul(*ptr, ptr, 10); if (val == 0) return 1; *rlim = val * mul; return 0; } limits_record_t limits_record_create(void) { struct limits_rec *lrec = xmalloc(sizeof(*lrec)); lrec->set = 0; return lrec; } /* Parse limits string and fill appropriate fields in lrec. The string consists of _commands_, optionally separated by any amount of whitespace. A command has the following form: [AaCcDdFfMmNnRrSsTtUuLlPp][0-9]+ i.e. a letter followed by number, and is interpreted as follows: Command ulimit setrlimit() The limit it sets option arg ------------------------------------------------------------- [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 Maximum filesize (KB) [Mm] m RLIMIT_MEMLOCK max locked-in-memory address space (KB) [Nn] n RLIMIT_NOFILE max number of open files [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 [Ll] l (none) max number of logins for this user [Pp] p (none) process priority -20..20 (negative = high priority) */ int limits_record_add(limits_record_t lrec, char *str, char **endp) { char *p; switch (*str++) { case 'a': case 'A': /* RLIMIT_AS - max address space (KB) */ if (getlimit(&str, &lrec->limit_as, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_AS; break; case 't': case 'T': /* RLIMIT_CPU - max CPU time (MIN) */ if (getlimit(&str, &lrec->limit_cpu, 60)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_CPU; break; case 'd': case 'D': /* RLIMIT_DATA - max data size (KB) */ if (getlimit(&str, &lrec->limit_data, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_DATA; break; case 'f': case 'F': /* RLIMIT_FSIZE - Maximum filesize (KB) */ if (getlimit(&str, &lrec->limit_fsize, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_FSIZE; break; case 'u': case 'U': /* RLIMIT_NPROC - max number of processes */ if (getlimit(&str, &lrec->limit_nproc, 1)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_NPROC; break; case 'c': case 'C': /* RLIMIT_CORE - max core file size (KB) */ if (getlimit(&str, &lrec->limit_core, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_CORE; break; case 'm': case 'M': /* RLIMIT_MEMLOCK - max locked-in-memory * address space (KB) */ if (getlimit(&str, &lrec->limit_memlock, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_MEMLOCK; break; case 'n': case 'N': /* RLIMIT_NOFILE - max number of open files */ if (getlimit(&str, &lrec->limit_nofile, 1)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_NOFILE; break; case 'r': case 'R': /* RLIMIT_RSS - max resident set size (KB) */ if (getlimit(&str, &lrec->limit_rss, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_RSS; break; case 's': case 'S': /* RLIMIT_STACK - max stack size (KB) */ if (getlimit(&str, &lrec->limit_stack, 1024)) { *endp = str; return lrec_badval; } lrec->set |= SET_LIMIT_STACK; break; case 'l': case 'L': lrec->limit_logins = strtoul(str, &p, 10); if (p == str) { *endp = p; return lrec_badval; } lrec->set |= SET_LIMIT_LOGINS; break; case 'p': case 'P': lrec->limit_prio = strtol(str, &p, 10); if (p == str) { *endp = p; return lrec_badval; } if (lrec->limit_prio > 0) lrec->set |= SET_LIMIT_PRIO; break; default: *endp = str-1; return lrec_error; } return 0; } int parse_limits(limits_record_t *plrec, char *str, char **endp) { int c; struct limits_rec *lrec = limits_record_create(); int rc; while ((c = *str++)) { if (ISWS(c)) continue; rc = limits_record_add(lrec, str, endp); if (rc) { free(lrec); return rc; } } *plrec = lrec; return 0; }