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
17 #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!
20 #include <security/_pam_aconf.h>
30 #include <sys/types.h>
32 #include <sys/resource.h>
35 #ifndef UT_USER /* some systems have ut_name instead of ut_user */
36 #define UT_USER ut_user
43 #define LINE_LENGTH 1024
45 #define LIMITS_DEF_USER 0 /* limit was set by an user entry */
46 #define LIMITS_DEF_GROUP 1 /* limit was set by a group entry */
47 #define LIMITS_DEF_DEFAULT 2 /* limit was set by an default entry */
48 #define LIMITS_DEF_NONE 3 /* this limit was not set yet */
50 static const char *limits_def_names[] = {
58 struct user_limits_struct {
66 int login_limit; /* the max logins limit */
67 int login_limit_def; /* which entry set the login limit */
68 int flag_numsyslogins; /* whether to limit logins only for a
69 specific user or to count all logins */
70 int priority; /* the priority to run user process with */
71 int supported[RLIM_NLIMITS];
72 struct user_limits_struct limits[RLIM_NLIMITS];
73 char conf_file[BUFSIZ];
76 #define LIMIT_LOGIN RLIM_NLIMITS+1
77 #define LIMIT_NUMSYSLOGINS RLIM_NLIMITS+2
79 #define LIMIT_PRI RLIM_NLIMITS+3
84 #define PAM_SM_SESSION
86 #include <security/pam_modules.h>
87 #include <security/_pam_macros.h>
90 static void _pam_log(int err, const char *format, ...)
94 va_start(args, format);
95 openlog("pam_limits", LOG_CONS|LOG_PID, LOG_AUTH);
96 vsyslog(err, format, args);
101 /* argument parsing */
103 #define PAM_DEBUG_ARG 0x0001
104 #define PAM_DO_SETREUID 0x0002
106 static int _pam_parse(int argc, const char **argv, struct pam_limit_s *pl)
110 /* step through arguments */
111 for (ctrl=0; argc-- > 0; ++argv) {
113 /* generic options */
115 if (!strcmp(*argv,"debug"))
116 ctrl |= PAM_DEBUG_ARG;
117 else if (!strncmp(*argv,"conf=",5))
118 strncpy(pl->conf_file,*argv+5,sizeof(pl->conf_file)-1);
119 else if (!strncmp(*argv,"change_uid",10))
120 ctrl |= PAM_DO_SETREUID;
122 _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
125 pl->conf_file[sizeof(pl->conf_file) - 1] = '\0';
132 #ifdef DEFAULT_CONF_FILE
133 # define LIMITS_FILE DEFAULT_CONF_FILE
135 # define LIMITS_FILE "/etc/security/limits.conf"
138 #define LIMIT_ERR 1 /* error setting a limit */
139 #define LOGIN_ERR 2 /* too many logins err */
141 /* checks if a user is on a list of members of the GID 0 group */
142 static int is_on_list(char * const *list, const char *member)
145 if (strcmp(*list, member) == 0)
153 * Checks if a user is a member of a group - return non-zero if
154 * the user is in the group.
156 static int is_in_group(const char *user_name, const char *group_name)
159 struct group *grp, *pgrp;
160 char uname[LINE_LENGTH], gname[LINE_LENGTH];
162 if (!user_name || !strlen(user_name))
164 if (!group_name || !strlen(group_name))
166 memset(uname, 0, sizeof(uname));
167 strncpy(uname, user_name, sizeof(uname)-1);
168 memset(gname, 0, sizeof(gname));
169 strncpy(gname, group_name, sizeof(gname)-1);
171 pwd = getpwnam(uname);
175 /* the info about this group */
176 grp = getgrnam(gname);
180 /* first check: is a member of the group_name group ? */
181 if (is_on_list(grp->gr_mem, uname))
184 /* next check: user primary group is group_name ? */
185 pgrp = getgrgid(pwd->pw_gid);
188 if (!strcmp(pgrp->gr_name, gname))
194 /* Counts the number of user logins and check against the limit*/
195 static int check_logins(const char *name, int limit, int ctrl,
196 struct pam_limit_s *pl)
201 if (ctrl & PAM_DEBUG_ARG) {
202 _pam_log(LOG_DEBUG, "checking logins for '%s' (maximum of %d)\n",
207 return 0; /* no limits imposed */
208 if (limit == 0) /* maximum 0 logins ? */ {
209 _pam_log(LOG_WARNING, "No logins allowed for '%s'\n", name);
215 while((ut = getutent())) {
217 if (ut->ut_type != USER_PROCESS)
220 if (ut->UT_USER[0] == '\0')
222 if (!pl->flag_numsyslogins) {
223 if ((pl->login_limit_def == LIMITS_DEF_USER)
224 && strncmp(name, ut->UT_USER, sizeof(ut->UT_USER)) != 0)
226 if ((pl->login_limit_def == LIMITS_DEF_GROUP)
227 && !is_in_group(ut->UT_USER, name))
236 _pam_log(LOG_WARNING, "Too many logins (max %d) for %s",
239 _pam_log(LOG_WARNING, "Too many system logins (max %d)", limit);
246 static int init_limits(struct pam_limit_s *pl)
249 int retval = PAM_SUCCESS;
253 for(i = 0; i < RLIM_NLIMITS; i++) {
254 int r = getrlimit(i, &pl->limits[i].limit);
256 if (errno == EINVAL) {
257 pl->supported[i] = 0;
259 retval = !PAM_SUCCESS;
262 pl->supported[i] = 1;
263 pl->limits[i].src_soft = LIMITS_DEF_NONE;
264 pl->limits[i].src_hard = LIMITS_DEF_NONE;
269 pl->login_limit = -2;
270 pl->login_limit_def = LIMITS_DEF_NONE;
275 static void process_limit(int source, const char *lim_type,
276 const char *lim_item, const char *lim_value,
277 int ctrl, struct pam_limit_s *pl)
282 const char **endptr = &lim_value;
283 const char *value_orig = lim_value;
285 if (ctrl & PAM_DEBUG_ARG)
286 _pam_log(LOG_DEBUG, "%s: processing %s %s %s for %s\n",
287 __FUNCTION__,lim_type,lim_item,lim_value,
288 limits_def_names[source]);
290 if (strcmp(lim_item, "cpu") == 0)
291 limit_item = RLIMIT_CPU;
292 else if (strcmp(lim_item, "fsize") == 0)
293 limit_item = RLIMIT_FSIZE;
294 else if (strcmp(lim_item, "data") == 0)
295 limit_item = RLIMIT_DATA;
296 else if (strcmp(lim_item, "stack") == 0)
297 limit_item = RLIMIT_STACK;
298 else if (strcmp(lim_item, "core") == 0)
299 limit_item = RLIMIT_CORE;
300 else if (strcmp(lim_item, "rss") == 0)
301 limit_item = RLIMIT_RSS;
302 else if (strcmp(lim_item, "nproc") == 0)
303 limit_item = RLIMIT_NPROC;
304 else if (strcmp(lim_item, "nofile") == 0)
305 limit_item = RLIMIT_NOFILE;
306 else if (strcmp(lim_item, "memlock") == 0)
307 limit_item = RLIMIT_MEMLOCK;
308 else if (strcmp(lim_item, "as") == 0)
309 limit_item = RLIMIT_AS;
311 else if (strcmp(lim_item, "locks") == 0)
312 limit_item = RLIMIT_LOCKS;
314 else if (strcmp(lim_item, "maxlogins") == 0) {
315 limit_item = LIMIT_LOGIN;
316 pl->flag_numsyslogins = 0;
317 } else if (strcmp(lim_item, "maxsyslogins") == 0) {
318 limit_item = LIMIT_NUMSYSLOGINS;
319 pl->flag_numsyslogins = 1;
320 } else if (strcmp(lim_item, "priority") == 0) {
321 limit_item = LIMIT_PRI;
323 _pam_log(LOG_DEBUG,"unknown limit item '%s'", lim_item);
327 if (strcmp(lim_type,"soft")==0)
328 limit_type=LIMIT_SOFT;
329 else if (strcmp(lim_type, "hard")==0)
330 limit_type=LIMIT_HARD;
331 else if (strcmp(lim_type,"-")==0)
332 limit_type=LIMIT_SOFT | LIMIT_HARD;
333 else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS) {
334 _pam_log(LOG_DEBUG,"unknown limit type '%s'", lim_type);
339 * there is a warning here because the library prototype for this
340 * function is incorrect.
342 limit_value = strtol(lim_value, endptr, 10);
344 if (limit_value == 0 && value_orig == *endptr) { /* no chars read */
345 if (strcmp(lim_value,"-") != 0) {
346 _pam_log(LOG_DEBUG,"wrong limit value '%s'", lim_value);
349 if (limit_item != LIMIT_LOGIN) {
350 if (ctrl & PAM_DEBUG_ARG)
352 "'-' limit value valid for maxlogins type only");
373 if ( (limit_item != LIMIT_LOGIN)
374 && (limit_item != LIMIT_NUMSYSLOGINS)
375 && (limit_item != LIMIT_PRI) ) {
376 if (limit_type & LIMIT_SOFT) {
377 if (pl->limits[limit_item].src_soft < source) {
380 pl->limits[limit_item].limit.rlim_cur = limit_value;
381 pl->limits[limit_item].src_soft = source;
384 if (limit_type & LIMIT_HARD) {
385 if (pl->limits[limit_item].src_hard < source) {
388 pl->limits[limit_item].limit.rlim_max = limit_value;
389 pl->limits[limit_item].src_hard = source;
393 if (limit_item == LIMIT_PRI) {
394 /* additional check */
395 pl->priority = ((limit_value>0)?limit_value:0);
397 if (pl->login_limit_def < source) {
400 pl->login_limit = limit_value;
401 pl->login_limit_def = source;
408 static int parse_config_file(const char *uname, int ctrl,
409 struct pam_limit_s *pl)
412 char buf[LINE_LENGTH];
414 #define CONF_FILE (pl->conf_file[0])?pl->conf_file:LIMITS_FILE
415 /* check for the LIMITS_FILE */
416 if (ctrl & PAM_DEBUG_ARG)
417 _pam_log(LOG_DEBUG,"reading settings from '%s'", CONF_FILE);
418 fil = fopen(CONF_FILE, "r");
420 _pam_log (LOG_WARNING, "can not read settings from %s", CONF_FILE);
421 return PAM_SERVICE_ERR;
426 memset(buf, 0, sizeof(buf));
428 while (fgets(buf, LINE_LENGTH, fil) != NULL) {
429 char domain[LINE_LENGTH];
430 char ltype[LINE_LENGTH];
431 char item[LINE_LENGTH];
432 char value[LINE_LENGTH];
437 /* skip the leading white space */
438 while (*tptr && isspace(*tptr))
440 strncpy(buf, tptr, sizeof(buf)-1);
441 buf[sizeof(buf)-1] = '\0';
443 /* Rip off the comments */
444 tptr = strchr(buf,'#');
447 /* Rip off the newline char */
448 tptr = strchr(buf,'\n');
451 /* Anything left ? */
453 memset(buf, 0, sizeof(buf));
457 memset(domain, 0, sizeof(domain));
458 memset(ltype, 0, sizeof(ltype));
459 memset(item, 0, sizeof(item));
460 memset(value, 0, sizeof(value));
462 i = sscanf(buf,"%s%s%s%s", domain, ltype, item, value);
463 D(("scanned line[%d]: domain[%s], ltype[%s], item[%s], value[%s]",
464 i, domain, ltype, item, value));
466 for(j=0; j < strlen(domain); j++)
467 domain[j]=tolower(domain[j]);
468 for(j=0; j < strlen(ltype); j++)
469 ltype[j]=tolower(ltype[j]);
470 for(j=0; j < strlen(item); j++)
471 item[j]=tolower(item[j]);
472 for(j=0; j < strlen(value); j++)
473 value[j]=tolower(value[j]);
475 if (i == 4) { /* a complete line */
476 if (strcmp(uname, domain) == 0) /* this user have a limit */
477 process_limit(LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
478 else if (domain[0]=='@') {
479 _pam_log(LOG_DEBUG, "checking if %s is in group %s",
481 if (is_in_group(uname, domain+1))
482 process_limit(LIMITS_DEF_GROUP, ltype, item, value, ctrl,
484 } else if (strcmp(domain, "*") == 0)
485 process_limit(LIMITS_DEF_DEFAULT, ltype, item, value, ctrl,
487 } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */
488 if (strcmp(uname, domain) == 0) {
489 _pam_log(LOG_DEBUG, "no limits for '%s'", uname);
492 } else if (domain[0] == '@' && is_in_group(uname, domain+1)) {
493 _pam_log(LOG_DEBUG, "no limits for '%s' in group '%s'",
499 _pam_log(LOG_DEBUG,"invalid line '%s' - skipped", buf);
506 static int setup_limits(const char * uname, int ctrl, struct pam_limit_s *pl)
509 int retval = PAM_SUCCESS;
511 for (i=0; i<RLIM_NLIMITS; i++) {
512 if (pl->limits[i].limit.rlim_cur > pl->limits[i].limit.rlim_max)
513 pl->limits[i].limit.rlim_cur = pl->limits[i].limit.rlim_max;
514 if (!pl->supported[i]) {
515 /* skip it if its not known to the system */
518 retval |= setrlimit(i, &pl->limits[i].limit);
521 if (retval != PAM_SUCCESS)
524 retval=setpriority(PRIO_PROCESS, 0, pl->priority);
526 if (retval != PAM_SUCCESS)
529 if (pl->login_limit > 0) {
530 if (check_logins(uname, pl->login_limit, ctrl, pl) == LOGIN_ERR)
532 } else if (pl->login_limit == 0)
537 /* now the session stuff */
538 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
539 int argc, const char **argv)
545 struct pam_limit_s pl;
549 memset(&pl, 0, sizeof(pl));
551 ctrl = _pam_parse(argc, argv, &pl);
552 retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
553 if ( user_name == NULL || retval != PAM_SUCCESS ) {
554 _pam_log(LOG_CRIT, "open_session - error recovering username");
555 return PAM_SESSION_ERR;
558 pwd = getpwnam(user_name);
560 if (ctrl & PAM_DEBUG_ARG)
561 _pam_log(LOG_WARNING, "open_session username '%s' does not exist",
563 return PAM_SESSION_ERR;
566 /* do not impose limits on UID 0 accounts */
568 if (ctrl & PAM_DEBUG_ARG)
569 _pam_log(LOG_DEBUG, "user '%s' have UID 0 - no limits imposed",
574 retval = init_limits(&pl);
575 if (retval != PAM_SUCCESS) {
576 _pam_log(LOG_WARNING, "cannot initialize");
580 retval = parse_config_file(pwd->pw_name, ctrl, &pl);
581 if (retval == PAM_IGNORE) {
582 D(("the configuration file has an applicable '<domain> -' entry"));
585 if (retval != PAM_SUCCESS) {
586 _pam_log(LOG_WARNING, "error parsing the configuration file");
590 if (ctrl & PAM_DO_SETREUID)
591 setreuid(pwd->pw_uid, -1);
592 retval = setup_limits(pwd->pw_name, ctrl, &pl);
593 if (retval & LOGIN_ERR) {
594 return PAM_PERM_DENIED;
600 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
601 int argc, const char **argv)
609 /* static module data */
611 struct pam_module _pam_limits_modstruct = {
617 pam_sm_close_session,
623 * Copyright (c) Cristian Gafton, 1996-1997, <gafton@redhat.com>
624 * All rights reserved.
626 * Redistribution and use in source and binary forms, with or without
627 * modification, are permitted provided that the following conditions
629 * 1. Redistributions of source code must retain the above copyright
630 * notice, and the entire permission notice in its entirety,
631 * including the disclaimer of warranties.
632 * 2. Redistributions in binary form must reproduce the above copyright
633 * notice, this list of conditions and the following disclaimer in the
634 * documentation and/or other materials provided with the distribution.
635 * 3. The name of the author may not be used to endorse or promote
636 * products derived from this software without specific prior
637 * written permission.
639 * ALTERNATIVELY, this product may be distributed under the terms of
640 * the GNU Public License, in which case the provisions of the GPL are
641 * required INSTEAD OF the above restrictions. (This clause is
642 * necessary due to a potential bad interaction between the GPL and
643 * the restrictions contained in a BSD-style copyright.)
645 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
646 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
647 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
648 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
649 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
650 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
651 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
652 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
653 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
654 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
655 * OF THE POSSIBILITY OF SUCH DAMAGE.