/* * pam_limits - impose resource limits when opening a user session * * 1.6 - modified for PLD (added process priority settings) * by Marcin Korzonek * 1.5 - Elliot Lee's "max system logins patch" * 1.4 - addressed bug in configuration file parser * 1.3 - modified the configuration file format * 1.2 - added 'debug' and 'conf=' arguments * 1.1 - added @group support * 1.0 - initial release - Linux ONLY * * See end for Copyright information */ #if !defined(linux) && !defined(__linux) #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!! #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef UT_USER /* some systems have ut_name instead of ut_user */ #define UT_USER ut_user #endif #include #include /* Module defines */ #ifndef LIMITS_FILE #define LIMITS_FILE "/etc/security/limits.conf" #endif #define LINE_LENGTH 1024 #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 */ struct user_limits_struct { int supported; int src_soft; int src_hard; struct rlimit limit; }; /* internal data */ struct pam_limit_s { int priority; /* the priority to run user process with */ struct user_limits_struct limits[RLIM_NLIMITS]; struct passwd *pwd; }; #define LIMIT_PRI RLIM_NLIMITS+1 #define LIMIT_SOFT 1 #define LIMIT_HARD 2 #define LIMITED_OK 0 /* limit setting appeared to work */ #define LIMIT_ERR 1 /* error setting a limit */ #define LOGIN_ERR 2 /* too many logins err */ typedef enum { LIMHANDLE_OK = 0, LIMHANDLE_ERROR = -1, LIMHANDLE_IGNORE = 1 } limhandle_t; static limhandle_t init_limits(struct pam_limit_s *pl) { int i, r; for(i = 0; i < RLIM_NLIMITS; i++) { errno = 0; r = getrlimit(i, &pl->limits[i].limit); if (r == -1) { pl->limits[i].supported = 0; if (errno != EINVAL) { g_printerr("Couldn't getrlimit: %s\n", g_strerror(errno)); return LIMHANDLE_ERROR; } } else { pl->limits[i].supported = 1; pl->limits[i].src_soft = LIMITS_DEF_NONE; pl->limits[i].src_hard = LIMITS_DEF_NONE; } } errno = 0; pl->priority = getpriority (PRIO_PROCESS, 0); if (pl->priority == -1 && errno != 0) { g_printerr("Couldn't getpriority: %s\n", g_strerror(errno)); return LIMHANDLE_ERROR; } return LIMHANDLE_OK; } static void process_limit (int source, const char *lim_type, const char *lim_item, const char *lim_value, struct pam_limit_s *pl) { int limit_item; int limit_type = 0; int int_value = 0; rlim_t rlimit_value = 0; char *endptr; const char *value_orig = lim_value; /* if (ctrl & PAM_DEBUG_ARG) pam_syslog(pamh, LOG_DEBUG, "%s: processing %s %s %s for %s", __FUNCTION__, lim_type, lim_item, lim_value, limits_def_names[source]);*/ if (strcmp(lim_item, "cpu") == 0) { limit_item = RLIMIT_CPU; } else if (strcmp(lim_item, "fsize") == 0) { limit_item = RLIMIT_FSIZE; } else if (strcmp(lim_item, "data") == 0) { limit_item = RLIMIT_DATA; } else if (strcmp(lim_item, "stack") == 0) { limit_item = RLIMIT_STACK; } else if (strcmp(lim_item, "core") == 0) { limit_item = RLIMIT_CORE; } else if (strcmp(lim_item, "rss") == 0) { limit_item = RLIMIT_RSS; } else if (strcmp(lim_item, "nproc") == 0) { limit_item = RLIMIT_NPROC; } else if (strcmp(lim_item, "nofile") == 0) { limit_item = RLIMIT_NOFILE; } else if (strcmp(lim_item, "memlock") == 0) { limit_item = RLIMIT_MEMLOCK; } else if (strcmp(lim_item, "as") == 0) { limit_item = RLIMIT_AS; #ifdef RLIMIT_LOCKS } else if (strcmp(lim_item, "locks") == 0) { limit_item = RLIMIT_LOCKS; #endif #ifdef RLIMIT_SIGPENDING } else if (strcmp(lim_item, "sigpending") == 0) { limit_item = RLIMIT_SIGPENDING; #endif #ifdef RLIMIT_MSGQUEUE } else if (strcmp(lim_item, "msgqueue") == 0) { limit_item = RLIMIT_MSGQUEUE; #endif #ifdef RLIMIT_NICE } else if (strcmp(lim_item, "nice") == 0) { limit_item = RLIMIT_NICE; #endif #ifdef RLIMIT_RTPRIO } else if (strcmp(lim_item, "rtprio") == 0) { limit_item = RLIMIT_RTPRIO; #endif } else if (strcmp(lim_item, "priority") == 0) { limit_item = LIMIT_PRI; } else { g_printerr("unknown limit item '%s'\n", lim_item); return; } if (strcmp(lim_type,"soft")==0) { limit_type=LIMIT_SOFT; } else if (strcmp(lim_type, "hard")==0) { limit_type=LIMIT_HARD; } else if (strcmp(lim_type,"-")==0) { limit_type=LIMIT_SOFT | LIMIT_HARD; } else { g_printerr("unknown limit type '%s'\n", lim_type); return; } if (limit_item != LIMIT_PRI #ifdef RLIMIT_NICE && limit_item != RLIMIT_NICE #endif && (strcmp(lim_value, "-1") == 0 || strcmp(lim_value, "-") == 0 || strcmp(lim_value, "unlimited") == 0 || strcmp(lim_value, "infinity") == 0)) { int_value = -1; rlimit_value = RLIM_INFINITY; } else if (limit_item == LIMIT_PRI #ifdef RLIMIT_NICE || limit_item == RLIMIT_NICE #endif ) { long temp; temp = strtol (lim_value, &endptr, 10); temp = temp < INT_MAX ? temp : INT_MAX; int_value = temp > INT_MIN ? temp : INT_MIN; if (int_value == 0 && value_orig == endptr) { g_printerr("wrong limit value '%s' for limit type '%s'\n", lim_value, lim_type); return; } } else { #ifdef __USE_FILE_OFFSET64 rlimit_value = strtoull (lim_value, &endptr, 10); #else rlimit_value = strtoul (lim_value, &endptr, 10); #endif if (rlimit_value == 0 && value_orig == endptr) { g_printerr("wrong limit value '%s' for limit type '%s'", lim_value, lim_type); return; } } switch(limit_item) { case RLIMIT_CPU: if (rlimit_value != RLIM_INFINITY) rlimit_value *= 60; break; case RLIMIT_FSIZE: case RLIMIT_DATA: case RLIMIT_STACK: case RLIMIT_CORE: case RLIMIT_RSS: case RLIMIT_MEMLOCK: case RLIMIT_AS: if (rlimit_value != RLIM_INFINITY) rlimit_value *= 1024; break; #ifdef RLIMIT_NICE case RLIMIT_NICE: if (int_value > 19) int_value = 19; rlimit_value = 19 - int_value; break; #endif } if (limit_item != LIMIT_PRI) { if (limit_type & LIMIT_SOFT) { if (pl->limits[limit_item].src_soft < source) { return; } else { pl->limits[limit_item].limit.rlim_cur = rlimit_value; pl->limits[limit_item].src_soft = source; } } if (limit_type & LIMIT_HARD) { if (pl->limits[limit_item].src_hard < source) { return; } else { pl->limits[limit_item].limit.rlim_max = rlimit_value; pl->limits[limit_item].src_hard = source; } } } else { /* recent kernels support negative priority limits (=raise priority) */ if (limit_item == LIMIT_PRI) { pl->priority = int_value; } } return; } static gboolean pam_modutil_user_in_group_nam_nam(struct passwd *pwd, const char *group) { struct group *grp; size_t i; grp = getgrnam(group); if (pwd == NULL || grp == NULL) return FALSE; if (pwd->pw_gid == grp->gr_gid) return TRUE; for (i = 0; (grp->gr_mem != NULL) && (grp->gr_mem[i] != NULL); i++) { if (strcmp(pwd->pw_name, grp->gr_mem[i]) == 0) { return TRUE; } } return FALSE; } static int parse_config_file(const char *conf_file, struct passwd *pwd, struct pam_limit_s *pl) { FILE *fil; char buf[LINE_LENGTH]; fil = fopen(conf_file, "r"); if (fil == NULL) { g_printerr("cannot read settings from '%s': %s", conf_file, g_strerror(errno)); return -1; } /* init things */ memset(buf, 0, sizeof(buf)); /* 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]; int i; size_t j; char *tptr; tptr = buf; /* skip the leading white space */ while (*tptr && isspace(*tptr)) tptr++; strncpy(buf, tptr, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; /* Rip off the comments */ tptr = strchr(buf,'#'); if (tptr) *tptr = '\0'; /* Rip off the newline char */ tptr = strchr(buf,'\n'); if (tptr) *tptr = '\0'; /* Anything left ? */ if (!strlen(buf)) { memset(buf, 0, sizeof(buf)); continue; } memset(domain, 0, sizeof(domain)); memset(ltype, 0, sizeof(ltype)); memset(item, 0, sizeof(item)); memset(value, 0, sizeof(value)); i = sscanf(buf,"%s%s%s%s", 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]); for(j=0; j < strlen(item); j++) item[j]=tolower(item[j]); for(j=0; j < strlen(value); j++) value[j]=tolower(value[j]); if (i == 4) { /* a complete line */ if (strcmp(pwd->pw_name, domain) == 0) /* this user have a limit */ process_limit(LIMITS_DEF_USER, ltype, item, value, pl); else if (domain[0]=='@') { if (pam_modutil_user_in_group_nam_nam(pwd, domain+1)) process_limit(LIMITS_DEF_GROUP, ltype, item, value, pl); } else if (domain[0]=='%') { if (strcmp(domain,"%") == 0) process_limit(LIMITS_DEF_ALL, ltype, item, value, pl); else if (pam_modutil_user_in_group_nam_nam(pwd, domain+1)) { process_limit(LIMITS_DEF_ALLGROUP, ltype, item, value, pl); } } else if (strcmp(domain, "*") == 0) { process_limit(LIMITS_DEF_DEFAULT, ltype, item, value, pl); } } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */ if (strcmp(pwd->pw_name, domain) == 0) { fclose(fil); return LIMHANDLE_IGNORE; } else if (domain[0] == '@' && pam_modutil_user_in_group_nam_nam(pwd, domain+1)) { fclose(fil); return LIMHANDLE_IGNORE; } } else { g_printerr("invalid line '%s' - skipped\n", buf); } } fclose(fil); return LIMHANDLE_OK; } static int setup_limits(struct pam_limit_s *pl) { int i; int status; int retval = LIMHANDLE_OK; for (i=0, status=LIMITED_OK; ilimits[i].supported) { /* skip it if its not known to the system */ continue; } if (pl->limits[i].src_soft == LIMITS_DEF_NONE && pl->limits[i].src_hard == LIMITS_DEF_NONE) { /* skip it if its not initialized */ continue; } if (pl->limits[i].limit.rlim_cur > pl->limits[i].limit.rlim_max) pl->limits[i].limit.rlim_cur = pl->limits[i].limit.rlim_max; status |= setrlimit(i, &pl->limits[i].limit); } if (status) { retval = LIMHANDLE_ERROR; } status = setpriority(PRIO_PROCESS, 0, pl->priority); if (status != 0) { retval = LIMHANDLE_ERROR; } return retval; } /* now the session stuff */ int pam_set_limits(const char *conf_file, const char *username); int pam_set_limits(const char *conf_file, const char *username) { int retval; struct pam_limit_s pl; struct passwd *pwd; pwd = getpwnam(username); if (NULL == pwd) return LIMHANDLE_ERROR; if (!conf_file) conf_file = LIMITS_FILE; memset(&pl, 0, sizeof(pl)); retval = init_limits(&pl); if (retval != LIMHANDLE_OK) { g_printerr("cannot initialize\n"); return retval; } retval = parse_config_file(conf_file, pwd, &pl); if (retval == LIMHANDLE_IGNORE) { return LIMHANDLE_OK; } if (retval != LIMHANDLE_OK) { g_printerr("error parsing the configuration file\n"); return retval; } retval = setup_limits(&pl); if (retval != LIMHANDLE_OK) { return retval; } return LIMHANDLE_OK; } /* * Copyright (c) Cristian Gafton, 1996-1997, * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */