2 * pam_limits - impose resource limits when opening a user session
4 * 1.6 - modified for PLD (added process priority settings)
5 * by Marcin Korzonek <mkorz@shadow.eu.org>
6 * 1.5 - Elliot Lee's "max system logins patch"
7 * 1.4 - addressed bug in configuration file parser
8 * 1.3 - modified the configuration file format
9 * 1.2 - added 'debug' and 'conf=' arguments
10 * 1.1 - added @group support
11 * 1.0 - initial release - Linux ONLY
13 * See end for Copyright information
16 #if !defined(linux) && !defined(__linux)
17 #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!
30 #include <sys/types.h>
32 #include <sys/resource.h>
36 #ifndef UT_USER /* some systems have ut_name instead of ut_user */
37 #define UT_USER ut_user
44 #define LINE_LENGTH 1024
46 #define LIMITS_DEF_USER 0 /* limit was set by an user entry */
47 #define LIMITS_DEF_GROUP 1 /* limit was set by a group entry */
48 #define LIMITS_DEF_ALLGROUP 2 /* limit was set by a group entry */
49 #define LIMITS_DEF_ALL 3 /* limit was set by an default entry */
50 #define LIMITS_DEF_DEFAULT 4 /* limit was set by an default entry */
51 #define LIMITS_DEF_NONE 5 /* this limit was not set yet */
53 static const char *limits_def_names[] = {
63 struct user_limits_struct {
72 int login_limit; /* the max logins limit */
73 int login_limit_def; /* which entry set the login limit */
74 int flag_numsyslogins; /* whether to limit logins only for a
75 specific user or to count all logins */
76 int priority; /* the priority to run user process with */
77 struct user_limits_struct limits[RLIM_NLIMITS];
78 const char *conf_file;
79 int utmp_after_pam_call;
80 char login_group[LINE_LENGTH];
83 #define LIMIT_LOGIN RLIM_NLIMITS+1
84 #define LIMIT_NUMSYSLOGINS RLIM_NLIMITS+2
86 #define LIMIT_PRI RLIM_NLIMITS+3
91 #define PAM_SM_SESSION
93 #include <security/pam_modules.h>
94 #include <security/_pam_macros.h>
95 #include <security/pam_modutil.h>
96 #include <security/pam_ext.h>
98 /* argument parsing */
100 #define PAM_DEBUG_ARG 0x0001
101 #define PAM_DO_SETREUID 0x0002
102 #define PAM_UTMP_EARLY 0x0004
104 /* Limits from globbed files. */
105 #define LIMITS_CONF_GLOB LIMITS_FILE_DIR
107 #define CONF_FILE (pl->conf_file != NULL)?pl->conf_file:LIMITS_FILE
110 _pam_parse (const pam_handle_t *pamh, int argc, const char **argv,
111 struct pam_limit_s *pl)
115 /* step through arguments */
116 for (ctrl=0; argc-- > 0; ++argv) {
118 /* generic options */
120 if (!strcmp(*argv,"debug")) {
121 ctrl |= PAM_DEBUG_ARG;
122 } else if (!strncmp(*argv,"conf=",5)) {
123 pl->conf_file = *argv+5;
124 } else if (!strncmp(*argv,"change_uid",10)) {
125 ctrl |= PAM_DO_SETREUID;
126 } else if (!strcmp(*argv,"utmp_early")) {
127 ctrl |= PAM_UTMP_EARLY;
129 pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
137 #define LIMITED_OK 0 /* limit setting appeared to work */
138 #define LIMIT_ERR 1 /* error setting a limit */
139 #define LOGIN_ERR 2 /* too many logins err */
141 /* Counts the number of user logins and check against the limit*/
143 check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
144 struct pam_limit_s *pl)
149 if (ctrl & PAM_DEBUG_ARG) {
150 pam_syslog(pamh, LOG_DEBUG,
151 "checking logins for '%s' (maximum of %d)", name, limit);
155 return 0; /* no limits imposed */
156 if (limit == 0) /* maximum 0 logins ? */ {
157 pam_syslog(pamh, LOG_WARNING, "No logins allowed for '%s'", name);
163 /* Because there is no definition about when an application
164 actually adds a utmp entry, some applications bizarrely do the
165 utmp call before the have PAM authenticate them to the system:
166 you're logged it, sort of...? Anyway, you can use the
167 "utmp_early" module argument in your PAM config file to make
168 allowances for this sort of problem. (There should be a PAM
169 standard for this, since if a module wants to actually map a
170 username then any early utmp entry will be for the unmapped
173 if (ctrl & PAM_UTMP_EARLY) {
179 while((ut = getutent())) {
181 if (ut->ut_type != USER_PROCESS) {
185 if (ut->UT_USER[0] == '\0') {
188 if (!pl->flag_numsyslogins) {
189 if (((pl->login_limit_def == LIMITS_DEF_USER)
190 || (pl->login_limit_def == LIMITS_DEF_GROUP)
191 || (pl->login_limit_def == LIMITS_DEF_DEFAULT))
192 && strncmp(name, ut->UT_USER, sizeof(ut->UT_USER)) != 0) {
195 if ((pl->login_limit_def == LIMITS_DEF_ALLGROUP)
196 && !pam_modutil_user_in_group_nam_nam(pamh, ut->UT_USER, pl->login_group)) {
200 if (++count > limit) {
207 pam_syslog(pamh, LOG_WARNING,
208 "Too many logins (max %d) for %s", limit, name);
210 pam_syslog(pamh, LOG_WARNING, "Too many system logins (max %d)", limit);
217 static int init_limits(struct pam_limit_s *pl)
220 int retval = PAM_SUCCESS;
224 for(i = 0; i < RLIM_NLIMITS; i++) {
225 int r = getrlimit(i, &pl->limits[i].limit);
227 pl->limits[i].supported = 0;
228 if (errno != EINVAL) {
229 retval = !PAM_SUCCESS;
232 pl->limits[i].supported = 1;
233 pl->limits[i].src_soft = LIMITS_DEF_NONE;
234 pl->limits[i].src_hard = LIMITS_DEF_NONE;
239 pl->priority = getpriority (PRIO_PROCESS, 0);
240 if (pl->priority == -1 && errno != 0)
241 retval = !PAM_SUCCESS;
242 pl->login_limit = -2;
243 pl->login_limit_def = LIMITS_DEF_NONE;
249 process_limit (const pam_handle_t *pamh, int source, const char *lim_type,
250 const char *lim_item, const char *lim_value,
251 int ctrl, struct pam_limit_s *pl)
256 rlim_t rlimit_value = 0;
258 const char *value_orig = lim_value;
260 if (ctrl & PAM_DEBUG_ARG)
261 pam_syslog(pamh, LOG_DEBUG, "%s: processing %s %s %s for %s",
262 __FUNCTION__, lim_type, lim_item, lim_value,
263 limits_def_names[source]);
265 if (strcmp(lim_item, "cpu") == 0)
266 limit_item = RLIMIT_CPU;
267 else if (strcmp(lim_item, "fsize") == 0)
268 limit_item = RLIMIT_FSIZE;
269 else if (strcmp(lim_item, "data") == 0)
270 limit_item = RLIMIT_DATA;
271 else if (strcmp(lim_item, "stack") == 0)
272 limit_item = RLIMIT_STACK;
273 else if (strcmp(lim_item, "core") == 0)
274 limit_item = RLIMIT_CORE;
275 else if (strcmp(lim_item, "rss") == 0)
276 limit_item = RLIMIT_RSS;
277 else if (strcmp(lim_item, "nproc") == 0)
278 limit_item = RLIMIT_NPROC;
279 else if (strcmp(lim_item, "nofile") == 0)
280 limit_item = RLIMIT_NOFILE;
281 else if (strcmp(lim_item, "memlock") == 0)
282 limit_item = RLIMIT_MEMLOCK;
283 else if (strcmp(lim_item, "as") == 0)
284 limit_item = RLIMIT_AS;
286 else if (strcmp(lim_item, "locks") == 0)
287 limit_item = RLIMIT_LOCKS;
289 #ifdef RLIMIT_SIGPENDING
290 else if (strcmp(lim_item, "sigpending") == 0)
291 limit_item = RLIMIT_SIGPENDING;
293 #ifdef RLIMIT_MSGQUEUE
294 else if (strcmp(lim_item, "msgqueue") == 0)
295 limit_item = RLIMIT_MSGQUEUE;
298 else if (strcmp(lim_item, "nice") == 0)
299 limit_item = RLIMIT_NICE;
302 else if (strcmp(lim_item, "rtprio") == 0)
303 limit_item = RLIMIT_RTPRIO;
305 else if (strcmp(lim_item, "maxlogins") == 0) {
306 limit_item = LIMIT_LOGIN;
307 pl->flag_numsyslogins = 0;
308 } else if (strcmp(lim_item, "maxsyslogins") == 0) {
309 limit_item = LIMIT_NUMSYSLOGINS;
310 pl->flag_numsyslogins = 1;
311 } else if (strcmp(lim_item, "priority") == 0) {
312 limit_item = LIMIT_PRI;
314 pam_syslog(pamh, LOG_DEBUG, "unknown limit item '%s'", lim_item);
318 if (strcmp(lim_type,"soft")==0)
319 limit_type=LIMIT_SOFT;
320 else if (strcmp(lim_type, "hard")==0)
321 limit_type=LIMIT_HARD;
322 else if (strcmp(lim_type,"-")==0)
323 limit_type=LIMIT_SOFT | LIMIT_HARD;
324 else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS) {
325 pam_syslog(pamh, LOG_DEBUG, "unknown limit type '%s'", lim_type);
328 if (limit_item != LIMIT_PRI
330 && limit_item != RLIMIT_NICE
332 && (strcmp(lim_value, "-1") == 0
333 || strcmp(lim_value, "-") == 0 || strcmp(lim_value, "unlimited") == 0
334 || strcmp(lim_value, "infinity") == 0)) {
336 rlimit_value = RLIM_INFINITY;
337 } else if (limit_item == LIMIT_PRI || limit_item == LIMIT_LOGIN ||
339 limit_item == RLIMIT_NICE ||
341 limit_item == LIMIT_NUMSYSLOGINS) {
343 temp = strtol (lim_value, &endptr, 10);
344 temp = temp < INT_MAX ? temp : INT_MAX;
345 int_value = temp > INT_MIN ? temp : INT_MIN;
346 if (int_value == 0 && value_orig == endptr) {
347 pam_syslog(pamh, LOG_DEBUG,
348 "wrong limit value '%s' for limit type '%s'",
349 lim_value, lim_type);
353 #ifdef __USE_FILE_OFFSET64
354 rlimit_value = strtoull (lim_value, &endptr, 10);
356 rlimit_value = strtoul (lim_value, &endptr, 10);
358 if (rlimit_value == 0 && value_orig == endptr) {
359 pam_syslog(pamh, LOG_DEBUG,
360 "wrong limit value '%s' for limit type '%s'",
361 lim_value, lim_type);
366 /* one more special case when limiting logins */
367 if ((source == LIMITS_DEF_ALL || source == LIMITS_DEF_ALLGROUP)
368 && (limit_item != LIMIT_LOGIN)) {
369 if (ctrl & PAM_DEBUG_ARG)
370 pam_syslog(pamh, LOG_DEBUG,
371 "'%%' domain valid for maxlogins type only");
377 if (rlimit_value != RLIM_INFINITY)
379 if (rlimit_value >= RLIM_INFINITY/60)
380 rlimit_value = RLIM_INFINITY;
392 if (rlimit_value != RLIM_INFINITY)
394 if (rlimit_value >= RLIM_INFINITY/1024)
395 rlimit_value = RLIM_INFINITY;
397 rlimit_value *= 1024;
404 rlimit_value = 19 - int_value;
409 if ( (limit_item != LIMIT_LOGIN)
410 && (limit_item != LIMIT_NUMSYSLOGINS)
411 && (limit_item != LIMIT_PRI) ) {
412 if (limit_type & LIMIT_SOFT) {
413 if (pl->limits[limit_item].src_soft < source) {
416 pl->limits[limit_item].limit.rlim_cur = rlimit_value;
417 pl->limits[limit_item].src_soft = source;
420 if (limit_type & LIMIT_HARD) {
421 if (pl->limits[limit_item].src_hard < source) {
424 pl->limits[limit_item].limit.rlim_max = rlimit_value;
425 pl->limits[limit_item].src_hard = source;
429 /* recent kernels support negative priority limits (=raise priority) */
431 if (limit_item == LIMIT_PRI) {
432 pl->priority = int_value;
434 if (pl->login_limit_def < source) {
437 pl->login_limit = int_value;
438 pl->login_limit_def = source;
445 static int parse_config_file(pam_handle_t *pamh, const char *uname, int ctrl,
446 struct pam_limit_s *pl)
449 char buf[LINE_LENGTH];
451 /* check for the LIMITS_FILE */
452 if (ctrl & PAM_DEBUG_ARG)
453 pam_syslog(pamh, LOG_DEBUG, "reading settings from '%s'", CONF_FILE);
454 fil = fopen(CONF_FILE, "r");
456 pam_syslog (pamh, LOG_WARNING,
457 "cannot read settings from %s: %m", CONF_FILE);
458 return PAM_SERVICE_ERR;
462 memset(buf, 0, sizeof(buf));
464 while (fgets(buf, LINE_LENGTH, fil) != NULL) {
465 char domain[LINE_LENGTH];
466 char ltype[LINE_LENGTH];
467 char item[LINE_LENGTH];
468 char value[LINE_LENGTH];
474 /* skip the leading white space */
475 while (*tptr && isspace(*tptr))
477 strncpy(buf, tptr, sizeof(buf)-1);
478 buf[sizeof(buf)-1] = '\0';
480 /* Rip off the comments */
481 tptr = strchr(buf,'#');
484 /* Rip off the newline char */
485 tptr = strchr(buf,'\n');
488 /* Anything left ? */
490 memset(buf, 0, sizeof(buf));
494 memset(domain, 0, sizeof(domain));
495 memset(ltype, 0, sizeof(ltype));
496 memset(item, 0, sizeof(item));
497 memset(value, 0, sizeof(value));
499 i = sscanf(buf,"%s%s%s%s", domain, ltype, item, value);
500 D(("scanned line[%d]: domain[%s], ltype[%s], item[%s], value[%s]",
501 i, domain, ltype, item, value));
503 for(j=0; j < strlen(ltype); j++)
504 ltype[j]=tolower(ltype[j]);
505 for(j=0; j < strlen(item); j++)
506 item[j]=tolower(item[j]);
507 for(j=0; j < strlen(value); j++)
508 value[j]=tolower(value[j]);
510 if (i == 4) { /* a complete line */
511 if (strcmp(uname, domain) == 0) /* this user have a limit */
512 process_limit(pamh, LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
513 else if (domain[0]=='@') {
514 if (ctrl & PAM_DEBUG_ARG) {
515 pam_syslog(pamh, LOG_DEBUG,
516 "checking if %s is in group %s",
519 if (pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1))
520 process_limit(pamh, LIMITS_DEF_GROUP, ltype, item, value, ctrl,
522 } else if (domain[0]=='%') {
523 if (ctrl & PAM_DEBUG_ARG) {
524 pam_syslog(pamh, LOG_DEBUG,
525 "checking if %s is in group %s",
528 if (strcmp(domain,"%") == 0)
529 process_limit(pamh, LIMITS_DEF_ALL, ltype, item, value, ctrl,
531 else if (pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1)) {
532 strcpy(pl->login_group, domain+1);
533 process_limit(pamh, LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
536 } else if (strcmp(domain, "*") == 0)
537 process_limit(pamh, LIMITS_DEF_DEFAULT, ltype, item, value, ctrl,
539 } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */
540 if (strcmp(uname, domain) == 0) {
541 if (ctrl & PAM_DEBUG_ARG) {
542 pam_syslog(pamh, LOG_DEBUG, "no limits for '%s'", uname);
546 } else if (domain[0] == '@' && pam_modutil_user_in_group_nam_nam(pamh, uname, domain+1)) {
547 if (ctrl & PAM_DEBUG_ARG) {
548 pam_syslog(pamh, LOG_DEBUG,
549 "no limits for '%s' in group '%s'",
556 pam_syslog(pamh, LOG_WARNING, "invalid line '%s' - skipped", buf);
563 static int setup_limits(pam_handle_t *pamh,
564 const char *uname, uid_t uid, int ctrl,
565 struct pam_limit_s *pl)
569 int retval = LIMITED_OK;
571 for (i=0, status=LIMITED_OK; i<RLIM_NLIMITS; i++) {
572 if (!pl->limits[i].supported) {
573 /* skip it if its not known to the system */
576 if (pl->limits[i].src_soft == LIMITS_DEF_NONE &&
577 pl->limits[i].src_hard == LIMITS_DEF_NONE) {
578 /* skip it if its not initialized */
581 if (pl->limits[i].limit.rlim_cur > pl->limits[i].limit.rlim_max)
582 pl->limits[i].limit.rlim_cur = pl->limits[i].limit.rlim_max;
583 status |= setrlimit(i, &pl->limits[i].limit);
590 status = setpriority(PRIO_PROCESS, 0, pl->priority);
596 D(("skip login limit check for uid=0"));
597 } else if (pl->login_limit > 0) {
598 if (check_logins(pamh, uname, pl->login_limit, ctrl, pl) == LOGIN_ERR) {
601 } else if (pl->login_limit == 0) {
608 /* now the session stuff */
610 pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED,
611 int argc, const char **argv)
619 struct pam_limit_s plstruct;
620 struct pam_limit_s *pl = &plstruct;
622 const char *oldlocale;
626 memset(pl, 0, sizeof(*pl));
627 memset(&globbuf, 0, sizeof(globbuf));
629 ctrl = _pam_parse(pamh, argc, argv, pl);
630 retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
631 if ( user_name == NULL || retval != PAM_SUCCESS ) {
632 pam_syslog(pamh, LOG_CRIT, "open_session - error recovering username");
633 return PAM_SESSION_ERR;
636 pwd = pam_modutil_getpwnam(pamh, user_name);
638 if (ctrl & PAM_DEBUG_ARG)
639 pam_syslog(pamh, LOG_WARNING,
640 "open_session username '%s' does not exist", user_name);
641 return PAM_USER_UNKNOWN;
644 retval = init_limits(pl);
645 if (retval != PAM_SUCCESS) {
646 pam_syslog(pamh, LOG_WARNING, "cannot initialize");
650 retval = parse_config_file(pamh, pwd->pw_name, ctrl, pl);
651 if (retval == PAM_IGNORE) {
652 D(("the configuration file ('%s') has an applicable '<domain> -' entry", CONF_FILE));
655 if (retval != PAM_SUCCESS || pl->conf_file != NULL)
656 /* skip reading limits.d if config file explicitely specified */
659 /* Read subsequent *.conf files, if they exist. */
661 /* set the LC_COLLATE so the sorting order doesn't depend
664 oldlocale = setlocale(LC_COLLATE, "C");
665 glob_rc = glob(LIMITS_CONF_GLOB, GLOB_ERR, NULL, &globbuf);
667 if (oldlocale != NULL)
668 setlocale (LC_COLLATE, oldlocale);
671 /* Parse the *.conf files. */
672 for (i = 0; globbuf.gl_pathv[i] != NULL; i++) {
673 pl->conf_file = globbuf.gl_pathv[i];
674 retval = parse_config_file(pamh, pwd->pw_name, ctrl, pl);
675 if (retval == PAM_IGNORE) {
676 D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file));
680 if (retval != PAM_SUCCESS)
687 if (retval != PAM_SUCCESS)
689 pam_syslog(pamh, LOG_WARNING, "error parsing the configuration file: '%s' ",CONF_FILE);
693 if (ctrl & PAM_DO_SETREUID) {
694 setreuid(pwd->pw_uid, -1);
697 retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, pl);
698 if (retval & LOGIN_ERR)
699 pam_error(pamh, _("Too many logins for '%s'."), pwd->pw_name);
700 if (retval != LIMITED_OK) {
701 return PAM_PERM_DENIED;
708 pam_sm_close_session (pam_handle_t *pamh UNUSED, int flags UNUSED,
709 int argc UNUSED, const char **argv UNUSED)
717 /* static module data */
719 struct pam_module _pam_limits_modstruct = {
725 pam_sm_close_session,
731 * Copyright (c) Cristian Gafton, 1996-1997, <gafton@redhat.com>
732 * All rights reserved.
734 * Redistribution and use in source and binary forms, with or without
735 * modification, are permitted provided that the following conditions
737 * 1. Redistributions of source code must retain the above copyright
738 * notice, and the entire permission notice in its entirety,
739 * including the disclaimer of warranties.
740 * 2. Redistributions in binary form must reproduce the above copyright
741 * notice, this list of conditions and the following disclaimer in the
742 * documentation and/or other materials provided with the distribution.
743 * 3. The name of the author may not be used to endorse or promote
744 * products derived from this software without specific prior
745 * written permission.
747 * ALTERNATIVELY, this product may be distributed under the terms of
748 * the GNU Public License, in which case the provisions of the GPL are
749 * required INSTEAD OF the above restrictions. (This clause is
750 * necessary due to a potential bad interaction between the GPL and
751 * the restrictions contained in a BSD-style copyright.)
753 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
754 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
755 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
756 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
757 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
758 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
759 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
760 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
761 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
762 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
763 * OF THE POSSIBILITY OF SUCH DAMAGE.